# -*- coding: utf-8 -*-
"""LVT4U — Layer Editor Dialog.

Manage, group, rename and style layers in the current QGIS project.

Author: Lộc Vũ Trung (LVT) / Slow Forest
License: GPL-3.0
"""

import json
import os

from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
    QComboBox, QGroupBox, QTableWidget, QTableWidgetItem,
    QHeaderView, QMessageBox, QAbstractItemView,
    QLineEdit, QCheckBox, QRadioButton,
    QButtonGroup, QFileDialog,
)
from qgis.core import (
    QgsProject, QgsVectorLayer, QgsRasterLayer,
    QgsWkbTypes, QgsStyle, QgsSingleSymbolRenderer,
    QgsSymbolLayerUtils,
)

from ..shared.i18n import tr, current_language

# ---------------------------------------------------------------------------
# Name suggestion dictionary
# ---------------------------------------------------------------------------
_NAME_HINTS = {
    "ranh_gioi": {"vi": "Ranh giới", "en": "Boundary"},
    "ranh_gioi_xa": {"vi": "Ranh giới xã", "en": "Commune boundary"},
    "ranh_gioi_huyen": {"vi": "Ranh giới huyện", "en": "District boundary"},
    "ranh_gioi_tinh": {"vi": "Ranh giới tỉnh", "en": "Province boundary"},
    "hien_trang": {"vi": "Hiện trạng rừng", "en": "Forest status"},
    "hien_trang_rung": {"vi": "Hiện trạng rừng", "en": "Forest status"},
    "rung": {"vi": "Rừng", "en": "Forest"},
    "dat_rung": {"vi": "Đất rừng", "en": "Forest land"},
    "lo": {"vi": "Lô rừng", "en": "Forest plot"},
    "khoanh": {"vi": "Khoảnh", "en": "Sub-compartment"},
    "tieu_khu": {"vi": "Tiểu khu", "en": "Compartment"},
    "tk": {"vi": "Tiểu khu", "en": "Compartment"},
    "duong": {"vi": "Đường", "en": "Road"},
    "duong_giao_thong": {"vi": "Đường giao thông", "en": "Road network"},
    "song": {"vi": "Sông suối", "en": "River/Stream"},
    "song_suoi": {"vi": "Sông suối", "en": "Rivers & streams"},
    "suoi": {"vi": "Suối", "en": "Stream"},
    "diem_chay": {"vi": "Điểm cháy", "en": "Fire point"},
    "tram": {"vi": "Trạm", "en": "Station"},
    "tram_kiem_lam": {"vi": "Trạm kiểm lâm", "en": "Forest ranger station"},
    "cay_trong": {"vi": "Cây trồng", "en": "Crops/Plantation"},
    "dan_cu": {"vi": "Dân cư", "en": "Settlement"},
    "mat_nuoc": {"vi": "Mặt nước", "en": "Water surface"},
    "quy_hoach": {"vi": "Quy hoạch", "en": "Planning zone"},
    "3_loai_rung": {"vi": "3 loại rừng", "en": "3 forest types"},
    "chu_rung": {"vi": "Chủ rừng", "en": "Forest owner"},
    "bien_gioi": {"vi": "Biên giới", "en": "National border"},
    "contour": {"vi": "Đường đồng mức", "en": "Contour"},
    "elevation": {"vi": "Cao độ", "en": "Elevation"},
    "slope": {"vi": "Độ dốc", "en": "Slope"},
    "dem": {"vi": "Mô hình số địa hình", "en": "DEM"},
    "ndvi": {"vi": "Chỉ số thực vật NDVI", "en": "NDVI Vegetation Index"},
}

# Raster rendering presets
_RASTER_PRESETS = {
    "NDVI": {
        "vi": "NDVI (Chỉ số thực vật)",
        "en": "NDVI (Vegetation Index)",
        "bands": "NIR-Red normalized",
    },
    "Hillshade": {
        "vi": "Hillshade (Bóng đổ địa hình)",
        "en": "Hillshade (Terrain shading)",
        "bands": "DEM shading",
    },
    "RGB_Natural": {
        "vi": "RGB Màu tự nhiên",
        "en": "RGB Natural Color",
        "bands": "R=4, G=3, B=2 (Sentinel-2) / R=4, G=3, B=2 (Landsat 8)",
    },
    "False_Color": {
        "vi": "Màu giả (NIR-R-G)",
        "en": "False Color (NIR-R-G)",
        "bands": "R=8, G=4, B=3 (Sentinel-2) / R=5, G=4, B=3 (Landsat 8)",
    },
}


def _suggest_name(layer_name):
    """Return (vi_name, en_name) suggestion based on layer name."""
    key = layer_name.lower().strip().replace(" ", "_").replace("-", "_")
    if key in _NAME_HINTS:
        h = _NAME_HINTS[key]
        return h["vi"], h["en"]
    # Partial match
    for k, v in _NAME_HINTS.items():
        if k in key or key in k:
            return v["vi"], v["en"]
    return layer_name, layer_name


# =====================================================================
class LayerEditorDialog(QDialog):
    """Layer Editor dialog for managing project layers."""

    def __init__(self, iface, parent=None):
        super().__init__(parent or iface.mainWindow())
        self.iface = iface
        self.setWindowTitle("LVT4U — " + tr("Layer Editor"))
        self.setWindowFlags(
            self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
        self.setMinimumWidth(900)
        self.setMinimumHeight(600)
        try:
            from qgis.PyQt.QtWidgets import QApplication
            screen = QApplication.primaryScreen().availableGeometry()
            w = int(screen.width() * 0.90)
            h = int(screen.height() * 0.90)
            self.resize(w, h)
            self.move(
                screen.x() + (screen.width() - w) // 2,
                screen.y() + (screen.height() - h) // 2,
            )
        except Exception:
            self.resize(1200, 800)

        self._layer_rows = []  # list of dicts per layer
        self._qml_dir = os.path.join(
            os.path.dirname(__file__), "styles")
        self._qml_catalog = self._scan_qml_styles()
        self._build_ui()

    # -----------------------------------------------------------------
    # UI Construction
    # -----------------------------------------------------------------
    def _build_ui(self):
        root = QVBoxLayout(self)
        root.setSpacing(8)

        # --- Top bar: language + help ---
        top = QHBoxLayout()
        top.addStretch()

        lang = current_language()
        lang_label = "VI ↔ EN" if lang == 'vi' else "EN ↔ VI"
        self.btn_lang = QPushButton(lang_label)
        self.btn_lang.setFixedHeight(30)
        self.btn_lang.setMinimumWidth(80)
        self.btn_lang.setStyleSheet(
            "QPushButton{background:#1565C0;color:white;"
            "font-weight:bold;padding:4px 12px;border-radius:4px}"
            "QPushButton:hover{background:#1976D2}")
        self.btn_lang.setToolTip(
            "Chuyển ngôn ngữ" if lang == 'vi' else "Toggle language")
        self.btn_lang.clicked.connect(self._toggle_lang)
        top.addWidget(self.btn_lang)

        help_label = "Hướng dẫn" if lang == 'vi' else "Help"
        self.btn_help = QPushButton("? " + help_label)
        self.btn_help.setFixedHeight(30)
        self.btn_help.setMinimumWidth(80)
        self.btn_help.setStyleSheet(
            "QPushButton{background:#F57F17;color:white;"
            "font-weight:bold;padding:4px 12px;border-radius:4px}"
            "QPushButton:hover{background:#FFA000}")
        self.btn_help.setToolTip(
            "Xem hướng dẫn sử dụng" if lang == 'vi'
            else "View user guide")
        self.btn_help.clicked.connect(self._show_help)
        top.addWidget(self.btn_help)
        root.addLayout(top)

        # --- Group mode ---
        grp_mode = QGroupBox(tr("Group Mode"))
        mode_ly = QHBoxLayout(grp_mode)
        self.rad_auto = QRadioButton(tr("Auto Group (by geometry)"))
        self.rad_manual = QRadioButton(tr("Manual Group"))
        self.rad_auto.setChecked(True)
        self._grp_mode = QButtonGroup()
        self._grp_mode.addButton(self.rad_auto, 0)
        self._grp_mode.addButton(self.rad_manual, 1)
        mode_ly.addWidget(self.rad_auto)
        mode_ly.addWidget(self.rad_manual)
        root.addWidget(grp_mode)

        # --- Column enable checkboxes ---
        col_ctl = QHBoxLayout()
        lang = current_language()
        warn_text = (
            "Bỏ chọn cột để không áp dụng thay đổi cho loại layer đó."
            if lang == 'vi' else
            "Uncheck a column to exclude that layer type from changes."
        )
        self.lbl_col_warn = QLabel(warn_text)
        self.lbl_col_warn.setStyleSheet(
            "color:#D32F2F;font-weight:bold;font-size:11px;")
        col_ctl.addWidget(self.lbl_col_warn)
        col_ctl.addStretch()

        self.chk_col_point = QCheckBox(
            "📍 " + tr("Point Layers"))
        self.chk_col_point.setChecked(True)
        self.chk_col_point.setStyleSheet("font-weight:bold;")
        self.chk_col_point.toggled.connect(
            lambda on: self._toggle_column("point", on))
        col_ctl.addWidget(self.chk_col_point)

        self.chk_col_line = QCheckBox(
            "📏 " + tr("Line Layers"))
        self.chk_col_line.setChecked(True)
        self.chk_col_line.setStyleSheet("font-weight:bold;")
        self.chk_col_line.toggled.connect(
            lambda on: self._toggle_column("line", on))
        col_ctl.addWidget(self.chk_col_line)

        self.chk_col_polygon = QCheckBox(
            "🔷 " + tr("Polygon Layers"))
        self.chk_col_polygon.setChecked(True)
        self.chk_col_polygon.setStyleSheet("font-weight:bold;")
        self.chk_col_polygon.toggled.connect(
            lambda on: self._toggle_column("polygon", on))
        col_ctl.addWidget(self.chk_col_polygon)
        root.addLayout(col_ctl)

        # --- 3-column layout: Point | Line | Polygon ---
        from qgis.PyQt.QtWidgets import QSplitter
        splitter = QSplitter(Qt.Horizontal)

        # Column 1: Point
        self.grp_point = self._make_group("📍 " + tr("Point Layers"))
        self.tbl_point = self._make_table()
        g_ly_pt = QVBoxLayout(self.grp_point)
        g_ly_pt.addWidget(self.tbl_point)
        splitter.addWidget(self.grp_point)

        # Column 2: Line
        self.grp_line = self._make_group("📏 " + tr("Line Layers"))
        self.tbl_line = self._make_table()
        g_ly_ln = QVBoxLayout(self.grp_line)
        g_ly_ln.addWidget(self.tbl_line)
        splitter.addWidget(self.grp_line)

        # Column 3: Polygon
        self.grp_polygon = self._make_group("🔷 " + tr("Polygon Layers"))
        self.tbl_polygon = self._make_table()
        g_ly_pg = QVBoxLayout(self.grp_polygon)
        g_ly_pg.addWidget(self.tbl_polygon)
        splitter.addWidget(self.grp_polygon)

        # Equal column widths
        splitter.setSizes([1, 1, 1])
        root.addWidget(splitter, 1)

        # --- Action buttons ---
        btn_ly = QHBoxLayout()
        self.btn_refresh = QPushButton("🔄 " + tr("Refresh"))
        self.btn_refresh.clicked.connect(self.refresh_layers)
        btn_ly.addWidget(self.btn_refresh)

        self.btn_apply = QPushButton("✅ " + tr("Apply All"))
        self.btn_apply.setStyleSheet(
            "QPushButton{background:#1B5E20;color:white;"
            "font-weight:bold;padding:8px 16px;border-radius:4px}"
            "QPushButton:hover{background:#2E7D32}")
        self.btn_apply.clicked.connect(self._on_apply)
        btn_ly.addWidget(self.btn_apply)

        self.btn_group = QPushButton("📋 " + tr("Group in Panel"))
        self.btn_group.clicked.connect(self._on_group_panel)
        btn_ly.addWidget(self.btn_group)

        btn_ly.addStretch()

        self.btn_save = QPushButton("💾 " + tr("Save Preset"))
        self.btn_save.clicked.connect(self._save_preset)
        btn_ly.addWidget(self.btn_save)

        self.btn_load = QPushButton("📂 " + tr("Load Preset"))
        self.btn_load.clicked.connect(self._load_preset)
        btn_ly.addWidget(self.btn_load)

        root.addLayout(btn_ly)

    def _make_group(self, title):
        grp = QGroupBox(title)
        grp.setStyleSheet(
            "QGroupBox{font-weight:bold;border:1px solid #ccc;"
            "border-radius:4px;margin-top:8px;padding-top:16px}"
            "QGroupBox::title{subcontrol-origin:margin;"
            "left:10px;padding:0 5px}")
        return grp

    def _make_table(self):
        """Create a vector layer table."""
        tbl = QTableWidget(0, 4)
        lang = current_language()
        headers = (
            ["☑", "Layer gốc", "Tên hiển thị", "Style"]
            if lang == 'vi' else
            ["☑", "Source Layer", "Display Name", "Style"]
        )
        tbl.setHorizontalHeaderLabels(headers)
        tbl.horizontalHeader().setStretchLastSection(False)
        tbl.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        tbl.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Interactive)
        tbl.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)
        tbl.horizontalHeader().setSectionResizeMode(
            3, QHeaderView.Stretch)
        tbl.setColumnWidth(1, 140)
        tbl.setSelectionBehavior(QAbstractItemView.SelectRows)
        tbl.setAlternatingRowColors(True)
        tbl.verticalHeader().setVisible(False)
        return tbl

    def _make_raster_table(self):
        """Create a raster layer table."""
        tbl = QTableWidget(0, 4)
        lang = current_language()
        headers = (
            ["☑", "Layer gốc", "Tên hiển thị", "Preset"]
            if lang == 'vi' else
            ["☑", "Source Layer", "Display Name", "Preset"]
        )
        tbl.setHorizontalHeaderLabels(headers)
        tbl.horizontalHeader().setStretchLastSection(False)
        tbl.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        tbl.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Interactive)
        tbl.horizontalHeader().setSectionResizeMode(
            2, QHeaderView.Stretch)
        tbl.horizontalHeader().setSectionResizeMode(
            3, QHeaderView.Interactive)
        tbl.setColumnWidth(1, 180)
        tbl.setColumnWidth(3, 200)
        tbl.setSelectionBehavior(QAbstractItemView.SelectRows)
        tbl.setAlternatingRowColors(True)
        tbl.verticalHeader().setVisible(False)
        return tbl

    # -----------------------------------------------------------------
    # Layer scanning
    # -----------------------------------------------------------------
    def refresh_layers(self):
        """Scan project and populate tables."""
        self._layer_rows.clear()
        polygons, lines, points, rasters = [], [], [], []

        for layer in QgsProject.instance().mapLayers().values():
            if isinstance(layer, QgsVectorLayer):
                geom = layer.geometryType()
                entry = {"layer": layer, "id": layer.id(),
                         "name": layer.name()}
                if geom == QgsWkbTypes.PolygonGeometry:
                    polygons.append(entry)
                elif geom == QgsWkbTypes.LineGeometry:
                    lines.append(entry)
                elif geom == QgsWkbTypes.PointGeometry:
                    points.append(entry)
            elif isinstance(layer, QgsRasterLayer):
                rasters.append({"layer": layer, "id": layer.id(),
                                "name": layer.name()})

        self._populate_vector_table(self.tbl_polygon, polygons, "polygon")
        self._populate_vector_table(self.tbl_line, lines, "line")
        self._populate_vector_table(self.tbl_point, points, "point")

        # Update group visibility
        self.grp_polygon.setVisible(len(polygons) > 0)
        self.grp_line.setVisible(len(lines) > 0)
        self.grp_point.setVisible(len(points) > 0)

    def _populate_vector_table(self, tbl, entries, geom_type):
        """Fill a vector table with layer entries."""
        tbl.setRowCount(len(entries))
        style_mgr = QgsStyle.defaultStyle()

        for row, e in enumerate(entries):
            layer = e["layer"]
            vi_name, en_name = _suggest_name(layer.name())
            lang = current_language()
            suggested = vi_name if lang == 'vi' else en_name

            # Col 0: Checkbox
            chk = QCheckBox()
            chk.setChecked(True)
            tbl.setCellWidget(row, 0, chk)

            # Col 1: Original name (read-only)
            item = QTableWidgetItem(layer.name())
            item.setFlags(item.flags() & ~Qt.ItemIsEditable)
            item.setData(Qt.UserRole, layer.id())
            tbl.setItem(row, 1, item)

            # Col 2: Display name (editable)
            edt = QLineEdit(suggested)
            tbl.setCellWidget(row, 2, edt)

            # Col 3: Style combo (filtered by geometry with icon preview)
            cmb = QComboBox()
            from qgis.PyQt.QtCore import QSize
            cmb.setIconSize(QSize(24, 24))
            cmb.addItem(
                "— " + (
                    "Giữ nguyên" if lang == 'vi' else "Keep current"
                ) + " —", None)
            self._populate_style_combo(cmb, geom_type, style_mgr)
            tbl.setCellWidget(row, 3, cmb)
            tbl.setRowHeight(row, 32)

            self._layer_rows.append({
                "layer": layer, "table": tbl, "row": row,
                "geom_type": geom_type, "chk": chk,
                "edt": edt, "cmb": cmb,
            })

    def _scan_qml_styles(self):
        """Scan bundled QML files and classify by geometry type."""
        catalog = {"polygon": [], "line": [], "point": []}
        if not os.path.isdir(self._qml_dir):
            return catalog
        for fname in sorted(os.listdir(self._qml_dir)):
            if not fname.lower().endswith('.qml'):
                continue
            fpath = os.path.join(self._qml_dir, fname)
            try:
                with open(fpath, 'r', encoding='utf-8',
                          errors='ignore') as f:
                    content = f.read(4000)
            except Exception:
                continue
            name_base = os.path.splitext(fname)[0]
            # Classify by content keywords
            low = fname.lower() + ' ' + content.lower()
            is_line = 'polyline' in low or 'line' in fname.lower()
            poly_keys = ('region', 'fill', 'polygon', 'bo_region')
            is_polygon = any(k in low for k in poly_keys) or 'sdd' in low.split()
            is_point = 'point' in low or 'font_point' in low
            if is_line:
                catalog["line"].append((name_base, fpath))
            elif is_polygon:
                catalog["polygon"].append((name_base, fpath))
            elif is_point:
                catalog["point"].append((name_base, fpath))
            else:
                # Categorized renderers are usually polygon
                if 'categorizedSymbol' in content:
                    catalog["polygon"].append((name_base, fpath))
                elif 'mergedFeature' in content:
                    catalog["line"].append((name_base, fpath))
                else:
                    catalog["polygon"].append((name_base, fpath))
        return catalog

    def _populate_style_combo(self, cmb, geom_type, style_mgr):
        """Add symbols with preview icons + bundled QML presets + SVG."""
        from qgis.core import QgsSymbol
        from qgis.PyQt.QtCore import QSize
        from qgis.PyQt.QtGui import QIcon, QPixmap
        lang = current_language()

        # --- Section 1: QGIS Style Manager symbols ---
        type_map = {
            "polygon": QgsSymbol.Fill,
            "line": QgsSymbol.Line,
            "point": QgsSymbol.Marker,
        }
        target_type = type_map.get(geom_type)
        if target_type is not None:
            preview_size = QSize(24, 24)
            added = 0
            for name in sorted(style_mgr.symbolNames()):
                sym = style_mgr.symbol(name)
                if sym and sym.type() == target_type:
                    icon = QgsSymbolLayerUtils.symbolPreviewIcon(
                        sym, preview_size)
                    cmb.addItem(icon, name, name)
                    added += 1

        # --- Section 2: SVG Markers (point only) ---
        if geom_type == "point":
            import os
            svg_base = os.path.join(
                os.environ.get("APPDATA", ""),
                "QGIS", "QGIS3", "profiles", "default",
                "svg", "LVT_Symbol"
            )
            if os.path.isdir(svg_base):
                cat_labels = {
                    "wildlife": ("Dong vat hoang da"
                                 if lang == 'vi' else "Wildlife"),
                    "forestry": ("Lam nghiep"
                                 if lang == 'vi' else "Forestry"),
                    "infrastructure": ("Ha tang"
                                       if lang == 'vi'
                                       else "Infrastructure"),
                    "marker": ("Ky hieu"
                               if lang == 'vi' else "Markers"),
                    "other": ("Khac" if lang == 'vi' else "Other"),
                }
                for cat_dir in sorted(os.listdir(svg_base)):
                    cat_path = os.path.join(svg_base, cat_dir)
                    if not os.path.isdir(cat_path):
                        continue
                    svgs = sorted([
                        f for f in os.listdir(cat_path)
                        if f.lower().endswith(".svg")
                    ])
                    if not svgs:
                        continue
                    # Category separator
                    label = cat_labels.get(
                        cat_dir, cat_dir.title())
                    cmb.insertSeparator(cmb.count())
                    sep_idx = cmb.count()
                    cmb.addItem(
                        "---- " + label + " ----")
                    model = cmb.model()
                    sep_item = model.item(sep_idx)
                    if sep_item:
                        sep_item.setEnabled(False)
                    # SVG items with preview
                    for svg_file in svgs:
                        svg_path = os.path.join(
                            cat_path, svg_file)
                        display = os.path.splitext(
                            svg_file)[0].replace("_", " ")
                        pix = QPixmap(svg_path)
                        if not pix.isNull():
                            icon = QIcon(
                                pix.scaled(
                                    24, 24,
                                    Qt.KeepAspectRatio,
                                    Qt.SmoothTransformation))
                        else:
                            icon = QIcon()
                        cmb.addItem(
                            icon, display,
                            "svg::" + svg_path)

        # --- Section 3: Bundled QML presets ---
        qml_list = self._qml_catalog.get(geom_type, [])
        if qml_list:
            sep_label = "---- QML Preset (LVT) ----"
            cmb.insertSeparator(cmb.count())
            idx = cmb.count()
            cmb.addItem(sep_label)
            model = cmb.model()
            item = model.item(idx)
            if item:
                item.setEnabled(False)

            for name, fpath in qml_list:
                # Store as "qml::<path>" to distinguish from symbols
                cmb.addItem("📄 " + name, "qml::" + fpath)

    def _populate_raster_table(self, tbl, entries):
        """Fill the raster table."""
        tbl.setRowCount(len(entries))
        lang = current_language()

        for row, e in enumerate(entries):
            layer = e["layer"]
            vi_name, en_name = _suggest_name(layer.name())
            suggested = vi_name if lang == 'vi' else en_name

            chk = QCheckBox()
            chk.setChecked(True)
            tbl.setCellWidget(row, 0, chk)

            item = QTableWidgetItem(layer.name())
            item.setFlags(item.flags() & ~Qt.ItemIsEditable)
            item.setData(Qt.UserRole, layer.id())
            tbl.setItem(row, 1, item)

            edt = QLineEdit(suggested)
            tbl.setCellWidget(row, 2, edt)

            cmb = QComboBox()
            cmb.addItem(
                "— " + (
                    "Giữ nguyên" if lang == 'vi' else "Keep current"
                ) + " —", None)
            for key, info in _RASTER_PRESETS.items():
                label = info[lang] if lang in info else info["en"]
                cmb.addItem(label, key)
            tbl.setCellWidget(row, 3, cmb)

            self._layer_rows.append({
                "layer": layer, "table": tbl, "row": row,
                "geom_type": "raster", "chk": chk,
                "edt": edt, "cmb": cmb,
            })

    # -----------------------------------------------------------------
    # Column toggle
    # -----------------------------------------------------------------
    def _toggle_column(self, geom_type, enabled):
        """Enable or disable a geometry column."""
        mapping = {
            "point": self.grp_point,
            "line": self.grp_line,
            "polygon": self.grp_polygon,
        }
        grp = mapping.get(geom_type)
        if grp:
            grp.setEnabled(enabled)
            grp.setStyleSheet(
                grp.styleSheet() if enabled
                else grp.styleSheet() + "QGroupBox{opacity:0.5;}"
            )

    # -----------------------------------------------------------------
    # Actions
    # -----------------------------------------------------------------
    def _on_apply(self):
        """Apply rename + style to all checked layers."""
        lang = current_language()
        # Determine which geometry columns are enabled
        enabled_types = set()
        if self.chk_col_point.isChecked():
            enabled_types.add("point")
        if self.chk_col_line.isChecked():
            enabled_types.add("line")
        if self.chk_col_polygon.isChecked():
            enabled_types.add("polygon")
        applied = 0
        for info in self._layer_rows:
            if info["geom_type"] not in enabled_types:
                continue
            if not info["chk"].isChecked():
                continue
            layer = info["layer"]
            new_name = info["edt"].text().strip()

            # Rename
            if new_name and new_name != layer.name():
                layer.setName(new_name)

            # Apply style (vector only)
            if info["geom_type"] != "raster":
                style_val = info["cmb"].currentData()
                if style_val:
                    if isinstance(style_val, str) and style_val.startswith(
                            "qml::"):
                        # Bundled QML preset
                        qml_path = style_val[5:]
                        layer.loadNamedStyle(qml_path)
                        layer.triggerRepaint()
                    elif isinstance(style_val, str) and style_val.startswith(
                            "svg::"):
                        # SVG Marker from LVT_Symbol
                        from qgis.core import (
                            QgsMarkerSymbol,
                            QgsSvgMarkerSymbolLayer,
                        )
                        svg_path = style_val[5:]
                        svg_lyr = QgsSvgMarkerSymbolLayer(svg_path)
                        svg_lyr.setSize(8)
                        marker = QgsMarkerSymbol()
                        marker.changeSymbolLayer(0, svg_lyr)
                        renderer = QgsSingleSymbolRenderer(marker)
                        layer.setRenderer(renderer)
                        layer.triggerRepaint()
                    else:
                        # QGIS Style Manager symbol
                        style_mgr = QgsStyle.defaultStyle()
                        sym = style_mgr.symbol(style_val)
                        if sym:
                            renderer = QgsSingleSymbolRenderer(
                                sym.clone())
                            layer.setRenderer(renderer)
                            layer.triggerRepaint()

            applied += 1

        # Refresh canvas
        self.iface.mapCanvas().refresh()
        try:
            root = QgsProject.instance().layerTreeRoot()
            model = self.iface.layerTreeView().layerTreeModel()
            for info in self._layer_rows:
                node = root.findLayer(info["layer"].id())
                if node:
                    model.refreshLayerLegend(node)
        except Exception:
            pass

        msg = (f"Đã áp dụng {applied} layer!"
               if lang == 'vi' else
               f"Applied to {applied} layers!")
        self.iface.messageBar().pushSuccess("LVT4U", msg)

    def _on_group_panel(self):
        """Create geometry-based groups in Layer Panel."""
        lang = current_language()
        root = QgsProject.instance().layerTreeRoot()

        group_names = {
            "polygon": "🔷 Polygon" if lang == 'en' else "🔷 Vùng",
            "line": "📏 Line" if lang == 'en' else "📏 Đường",
            "point": "📍 Point" if lang == 'en' else "📍 Điểm",
            "raster": "🛰️ Raster" if lang == 'en' else "🛰️ Ảnh",
        }

        # Create groups
        groups = {}
        for gtype, gname in group_names.items():
            existing = root.findGroup(gname)
            if existing:
                groups[gtype] = existing
            else:
                groups[gtype] = root.addGroup(gname)

        # Move layers
        moved = 0
        for info in self._layer_rows:
            if not info["chk"].isChecked():
                continue
            layer = info["layer"]
            gtype = info["geom_type"]
            target_group = groups.get(gtype)
            if not target_group:
                continue

            node = root.findLayer(layer.id())
            if node and node.parent() != target_group:
                clone = node.clone()
                target_group.insertChildNode(0, clone)
                node.parent().removeChildNode(node)
                moved += 1

        # Remove empty groups
        for gtype, grp in groups.items():
            if len(grp.children()) == 0:
                root.removeChildNode(grp)

        msg = (f"Đã nhóm {moved} layer!"
               if lang == 'vi' else
               f"Grouped {moved} layers!")
        self.iface.messageBar().pushSuccess("LVT4U", msg)

    def _on_suggest(self, layer):
        """Suggest opening Thematic Map for this layer."""
        lang = current_language()
        msg = (
            "Mở Bản đồ chuyên đề (TT16) để thiết lập style\n"
            "cho layer này?"
            if lang == 'vi' else
            "Open Thematic Map (TT16) to configure style\n"
            "for this layer?"
        )
        reply = QMessageBox.question(
            self, "LVT4U", msg,
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            # Find main plugin and open thematic map
            try:
                self.iface.mainWindow().menuBar()  # trigger menu
            except Exception:
                pass
            QMessageBox.information(
                self, "LVT4U",
                "Vui lòng mở từ menu:\n"
                "LVT4U → Forest Status Thematic Map"
                if lang == 'vi' else
                "Please open from menu:\n"
                "LVT4U → Forest Status Thematic Map"
            )

    # -----------------------------------------------------------------
    # Preset Save/Load
    # -----------------------------------------------------------------
    def _save_preset(self):
        lang = current_language()
        path, _ = QFileDialog.getSaveFileName(
            self,
            "Lưu Preset" if lang == 'vi' else "Save Preset",
            "", "JSON (*.json)")
        if not path:
            return
        data = []
        for info in self._layer_rows:
            data.append({
                "layer_id": info["layer"].id(),
                "layer_name": info["layer"].name(),
                "display_name": info["edt"].text(),
                "style": info["cmb"].currentData(),
                "checked": info["chk"].isChecked(),
                "geom_type": info["geom_type"],
            })
        with open(path, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        QMessageBox.information(
            self, "LVT4U",
            f"Đã lưu: {path}" if lang == 'vi' else f"Saved: {path}")

    def _load_preset(self):
        lang = current_language()
        path, _ = QFileDialog.getOpenFileName(
            self,
            "Tải Preset" if lang == 'vi' else "Load Preset",
            "", "JSON (*.json)")
        if not path or not os.path.isfile(path):
            return
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)

        # Match by layer_id or layer_name
        for preset in data:
            for info in self._layer_rows:
                lid = info["layer"].id()
                lname = info["layer"].name()
                id_ok = lid == preset.get("layer_id")
                name_ok = lname == preset.get("layer_name")
                if id_ok or name_ok:
                    info["edt"].setText(preset.get("display_name", lname))
                    info["chk"].setChecked(preset.get("checked", True))
                    style_val = preset.get("style")
                    if style_val:
                        idx = info["cmb"].findData(style_val)
                        if idx >= 0:
                            info["cmb"].setCurrentIndex(idx)
                    break

        QMessageBox.information(
            self, "LVT4U",
            f"Đã tải: {path}" if lang == 'vi' else f"Loaded: {path}")

    # -----------------------------------------------------------------
    # Language toggle
    # -----------------------------------------------------------------
    def _toggle_lang(self):
        from ..shared.i18n import set_language
        new_lang = 'en' if current_language() == 'vi' else 'vi'
        set_language(new_lang)

        # Rebuild all UI text
        lang = current_language()
        self.setWindowTitle("LVT4U — " + tr("Layer Editor"))

        # Language + Help buttons
        self.btn_lang.setText(
            "VI \u2194 EN" if lang == 'vi' else "EN \u2194 VI")
        self.btn_lang.setToolTip(
            "Chuy\u1ec3n ng\u00f4n ng\u1eef" if lang == 'vi'
            else "Toggle language")
        self.btn_help.setText(
            "? H\u01b0\u1edbng d\u1eabn" if lang == 'vi' else "? Help")
        self.btn_help.setToolTip(
            "Xem h\u01b0\u1edbng d\u1eabn s\u1eed d\u1ee5ng" if lang == 'vi'
            else "View user guide")

        # Group titles
        self.grp_point.setTitle("\ud83d\udccd " + tr("Point Layers"))
        self.grp_line.setTitle("\ud83d\udccf " + tr("Line Layers"))
        self.grp_polygon.setTitle("\ud83d\udd37 " + tr("Polygon Layers"))

        # Radio buttons
        self.rad_auto.setText(tr("Auto Group (by geometry)"))
        self.rad_manual.setText(tr("Manual Group"))

        # Action buttons
        self.btn_refresh.setText("\ud83d\udd04 " + tr("Refresh"))
        self.btn_apply.setText("\u2705 " + tr("Apply All"))
        self.btn_group.setText("\ud83d\udccb " + tr("Group in Panel"))
        self.btn_save.setText("\ud83d\udcbe " + tr("Save Preset"))
        self.btn_load.setText("\ud83d\udcc2 " + tr("Load Preset"))

        # Column enable checkboxes
        self.chk_col_point.setText("\ud83d\udccd " + tr("Point Layers"))
        self.chk_col_line.setText("\ud83d\udccf " + tr("Line Layers"))
        self.chk_col_polygon.setText("\ud83d\udd37 " + tr("Polygon Layers"))
        warn_text = (
            "B\u1ecf ch\u1ecdn c\u1ed9t \u0111\u1ec3 kh\u00f4ng \u00e1p d\u1ee5ng thay \u0111\u1ed5i cho lo\u1ea1i layer \u0111\u00f3."
            if lang == 'vi' else
            "Uncheck a column to exclude that layer type from changes."
        )
        self.lbl_col_warn.setText(warn_text)

        # Table headers
        lang = current_language()
        vec_headers = (
            ["☑", "Layer gốc", "Tên hiển thị", "Style"]
            if lang == 'vi' else
            ["☑", "Source Layer", "Display Name", "Style"]
        )
        for tbl in (self.tbl_point, self.tbl_line, self.tbl_polygon):
            tbl.setHorizontalHeaderLabels(vec_headers)

        # Refresh layer data with new language names
        self.refresh_layers()

    # -----------------------------------------------------------------
    # Help
    # -----------------------------------------------------------------
    def _show_help(self):
        from ..shared.help_dialog import HelpDialog
        dlg = HelpDialog("layer_editor", parent=self)
        dlg.exec_()
