# -*- coding: utf-8 -*-
from functools import partial
import os

from qgis.core import (
    QgsProject, QgsVectorLayer, QgsCoordinateTransform,
    QgsCoordinateReferenceSystem, QgsFeatureRequest, QgsGeometry, QgsMessageLog, QgsProviderRegistry, QgsSpatialIndex, QgsWkbTypes, Qgis
)
from qgis.utils import iface
from qgis.PyQt.QtCore import QTimer, QSettings
from qgis.PyQt.QtWidgets import QAction, QDialog, QVBoxLayout, QLabel, QSpinBox, QPushButton, QHBoxLayout


class DelaySettingDialog(QDialog):
    def __init__(self, delay_ms: int, parent=None):
        super().__init__(parent)
        self.setWindowTitle("自动保存延迟时间")
        self.setModal(True)
        layout = QVBoxLayout(self)
        layout.addWidget(QLabel("设置编辑后多久自动保存（毫秒）："))
        self.spin = QSpinBox()
        self.spin.setRange(0, 600000)
        self.spin.setSingleStep(100)
        self.spin.setValue(int(delay_ms))
        layout.addWidget(self.spin)
        btn_row = QHBoxLayout()
        ok_btn = QPushButton("确定")
        ok_btn.clicked.connect(self.accept)
        cancel_btn = QPushButton("取消")
        cancel_btn.clicked.connect(self.reject)
        btn_row.addWidget(ok_btn)
        btn_row.addWidget(cancel_btn)
        layout.addLayout(btn_row)

    def value_ms(self) -> int:
        return int(self.spin.value())


class AutoSavePlugin:
    """自动保存插件：无中断保存 + 面积自动更新（QGIS 4.0修正版）"""

    def __init__(self, iface):
        self.iface = iface
        self._commit_timers = {}          # 图层 -> QTimer
        self._auto_save_enabled = set()    # 已启用自动保存的图层
        self._settings = QSettings()
        self.delay_ms = int(self._settings.value("AutoSavePlugin/delay_ms", 1500))
        self.action = None
        self.action_settings = None
        self._saving = False               # 防止递归保存的标志
        self._admin_index_cache = {}
        self._admin_warned_no_layer = False
        self._admin_shp_layer = None
        self._admin_shp_path = ""

    def log(self, msg, level=Qgis.Info):
        QgsMessageLog.logMessage(msg, "AutoSavePlugin", level)

    def _safe_str(self, obj):
        try:
            return str(obj)
        except Exception:
            return "<unprintable>"

    def _pick_identity_fields(self, layer):
        fields = layer.fields()
        preferred = ["id", "uuid", "gid", "objectid", "fid", "name", "编号", "名称"]
        picked = []
        for n in preferred:
            idx = fields.indexFromName(n)
            if idx != -1:
                picked.append(n)
        return picked[:3]

    def _feature_identity_text(self, layer, feature, identity_fields):
        parts = ["fid=%s" % self._safe_str(feature.id())]
        for n in identity_fields:
            try:
                parts.append("%s=%s" % (n, self._safe_str(feature[n])))
            except Exception:
                continue
        return ", ".join(parts)

    def _collect_pending_change_fids(self, layer):
        edit_buffer = layer.editBuffer()
        if not edit_buffer:
            return [], [], [], []

        try:
            added = list(edit_buffer.addedFeatures().keys())
        except Exception:
            try:
                added = list(edit_buffer.addedFeatures())
            except Exception:
                added = []

        try:
            changed_geom = list(edit_buffer.changedGeometries().keys())
        except Exception:
            changed_geom = []

        try:
            changed_attr = list(edit_buffer.changedAttributeValues().keys())
        except Exception:
            changed_attr = []

        try:
            deleted = list(edit_buffer.deletedFeatureIds())
        except Exception:
            deleted = []

        return added, changed_geom, changed_attr, deleted

    def _points_equal(self, p1, p2):
        try:
            return float(p1.x()) == float(p2.x()) and float(p1.y()) == float(p2.y())
        except Exception:
            return False

    def _ring_min_points_issue(self, geom):
        try:
            if QgsWkbTypes.geometryType(geom.wkbType()) != QgsWkbTypes.PolygonGeometry:
                return None

            if QgsWkbTypes.isMultiType(geom.wkbType()):
                polys = geom.asMultiPolygon()
                for pi, poly in enumerate(polys):
                    for ri, ring in enumerate(poly):
                        if len(ring) < 4:
                            return "Polygon ring points<4 (poly=%d ring=%d points=%d)" % (pi, ri, len(ring))
                        if not self._points_equal(ring[0], ring[-1]):
                            return "Polygon ring not closed (poly=%d ring=%d)" % (pi, ri)
            else:
                poly = geom.asPolygon()
                for ri, ring in enumerate(poly):
                    if len(ring) < 4:
                        return "Polygon ring points<4 (ring=%d points=%d)" % (ri, len(ring))
                    if not self._points_equal(ring[0], ring[-1]):
                        return "Polygon ring not closed (ring=%d)" % ri
        except Exception:
            return None

        return None

    def _precheck_invalid_geometries(self, layer):
        added_fids, changed_geom_fids, changed_attr_fids, deleted_fids = self._collect_pending_change_fids(layer)
        candidate_fids = sorted(set(added_fids) | set(changed_geom_fids))
        if not candidate_fids:
            return True

        identity_fields = self._pick_identity_fields(layer)
        invalids = []
        for fid in candidate_fids[:500]:
            feat = layer.getFeature(fid)
            if not feat.isValid():
                invalids.append(("fid=%s" % self._safe_str(fid), "无法从图层获取要素"))
                continue

            geom = feat.geometry()
            if geom is None or geom.isNull() or geom.isEmpty():
                invalids.append((self._feature_identity_text(layer, feat, identity_fields), "几何为空"))
                continue

            issue = self._ring_min_points_issue(geom)
            if issue:
                invalids.append((self._feature_identity_text(layer, feat, identity_fields), issue))
                continue

            try:
                if not geom.isGeosValid():
                    try:
                        geom_errs = geom.validateGeometry()
                        geom_errs_text = "; ".join([self._safe_str(e) for e in geom_errs[:5]])
                    except Exception as e:
                        geom_errs_text = "validateGeometry失败: %s" % self._safe_str(e)
                    invalids.append((self._feature_identity_text(layer, feat, identity_fields), geom_errs_text or "GEOS无效"))
            except Exception:
                pass

        if not invalids:
            return True

        self.log("阻止自动保存：发现无效几何（图层='%s'），请先修复后再保存" % layer.name(), Qgis.Critical)
        for k, reason in invalids[:200]:
            self.log("无效要素：%s reason=%s" % (k, reason), Qgis.Critical)

        self.iface.messageBar().pushMessage(
            "自动保存",
            "图层 '{}' 发现无效多边形，已阻止自动保存（详见日志）".format(layer.name()),
            level=Qgis.Critical,
            duration=6,
        )
        return False

    def _log_commit_failure_details(self, layer, commit_errors):
        source = layer.source() if hasattr(layer, "source") else ""
        provider = layer.providerType() if hasattr(layer, "providerType") else ""
        added_fids, changed_geom_fids, changed_attr_fids, deleted_fids = self._collect_pending_change_fids(layer)
        pending_fids = sorted(set(added_fids) | set(changed_geom_fids) | set(changed_attr_fids))

        self.log(
            "保存失败（图层='%s' provider=%s）source=%s" % (layer.name(), provider, source),
            Qgis.Critical,
        )
        self.log(
            "未提交变更：新增=%d 修改几何=%d 修改属性=%d 删除=%d"
            % (len(added_fids), len(changed_geom_fids), len(changed_attr_fids), len(deleted_fids)),
            Qgis.Critical,
        )

        if commit_errors:
            if isinstance(commit_errors, (list, tuple)):
                err_text = " | ".join([self._safe_str(e) for e in commit_errors])
            else:
                err_text = self._safe_str(commit_errors)
            self.log("提交错误：%s" % err_text, Qgis.Critical)

        identity_fields = self._pick_identity_fields(layer)
        for fid in pending_fids[:200]:
            feat = layer.getFeature(fid)
            if not feat.isValid():
                self.log("疑似失败要素：fid=%s（无法从图层获取要素）" % self._safe_str(fid), Qgis.Critical)
                continue

            geom = feat.geometry()
            if geom is None or geom.isNull() or geom.isEmpty():
                self.log(
                    "疑似失败要素：%s geom=EMPTY"
                    % self._feature_identity_text(layer, feat, identity_fields),
                    Qgis.Critical,
                )
                continue

            geom_valid = True
            geom_errs_text = ""
            try:
                geom_valid = geom.isGeosValid()
            except Exception:
                geom_valid = True

            if not geom_valid:
                try:
                    geom_errs = geom.validateGeometry()
                    geom_errs_text = "; ".join([self._safe_str(e) for e in geom_errs[:5]])
                except Exception as e:
                    geom_errs_text = "validateGeometry失败: %s" % self._safe_str(e)

            bbox = ""
            try:
                b = geom.boundingBox()
                bbox = "bbox=(%.6f, %.6f, %.6f, %.6f)" % (b.xMinimum(), b.yMinimum(), b.xMaximum(), b.yMaximum())
            except Exception:
                bbox = ""

            if not geom_valid:
                self.log(
                    "疑似失败要素：%s geomValid=false %s errors=%s"
                    % (self._feature_identity_text(layer, feat, identity_fields), bbox, geom_errs_text),
                    Qgis.Critical,
                )
            else:
                self.log(
                    "疑似失败要素：%s geomValid=true %s"
                    % (self._feature_identity_text(layer, feat, identity_fields), bbox),
                    Qgis.Critical,
                )

    def _find_admin_layer(self):
        shp_path = self._settings.value("AutoSavePlugin/admin_shp_path", "", type=str).strip()
        if shp_path:
            if self._admin_shp_layer and self._admin_shp_path == shp_path and self._admin_shp_layer.isValid():
                return self._admin_shp_layer
            lyr = self._load_admin_layer_from_path(shp_path)
            if lyr and lyr.isValid():
                self._admin_shp_layer = lyr
                self._admin_shp_path = shp_path
                return lyr

        plugin_dir = os.path.dirname(os.path.abspath(__file__))
        preferred_names = ["XZQH.gpkg", "admin.gpkg", "行政区划.gpkg", "admin.shp", "行政区划.shp", "xzqh.shp"]
        preferred_paths = [os.path.join(plugin_dir, n) for n in preferred_names]
        candidates = [p for p in preferred_paths if os.path.isfile(p)]
        if not candidates:
            try:
                candidates = []
                for root, dirs, files in os.walk(plugin_dir):
                    rel = os.path.relpath(root, plugin_dir)
                    if rel != "." and os.sep in rel:
                        continue
                    for n in files:
                        if n.lower().endswith((".shp", ".gpkg")):
                            candidates.append(os.path.join(root, n))
            except Exception:
                candidates = []

        for p in candidates:
            if self._admin_shp_layer and self._admin_shp_path == p and self._admin_shp_layer.isValid():
                return self._admin_shp_layer
            lyr = self._load_admin_layer_from_path(p)
            if not lyr or not lyr.isValid():
                continue
            self._admin_shp_layer = lyr
            self._admin_shp_path = p
            return lyr

        preferred_id = self._settings.value("AutoSavePlugin/admin_layer_id", "", type=str)
        if preferred_id:
            lyr = QgsProject.instance().mapLayer(preferred_id)
            if isinstance(lyr, QgsVectorLayer) and QgsWkbTypes.geometryType(lyr.wkbType()) == QgsWkbTypes.PolygonGeometry:
                return lyr

        candidates = []
        for lyr in QgsProject.instance().mapLayers().values():
            if not isinstance(lyr, QgsVectorLayer):
                continue
            if QgsWkbTypes.geometryType(lyr.wkbType()) != QgsWkbTypes.PolygonGeometry:
                continue
            f = lyr.fields()
            if f.indexFromName("name") != -1 and f.indexFromName("gb") != -1:
                candidates.append(lyr)
        if len(candidates) == 1:
            return candidates[0]

        keywords = self._settings.value("AutoSavePlugin/admin_layer_keywords", "行政区划,区划,边界,boundary,admin", type=str)
        keywords = [k.strip().lower() for k in keywords.split(",") if k.strip()]
        for lyr in QgsProject.instance().mapLayers().values():
            if not isinstance(lyr, QgsVectorLayer):
                continue
            if QgsWkbTypes.geometryType(lyr.wkbType()) != QgsWkbTypes.PolygonGeometry:
                continue
            n = (lyr.name() or "").lower()
            if any(k in n for k in keywords):
                return lyr
        return None

    def _load_admin_layer_from_path(self, path):
        if not path:
            return None

        p = str(path)
        if p.lower().endswith(".gpkg"):
            return self._load_admin_layer_from_gpkg(p)

        lyr = QgsVectorLayer(p, "行政区划(插件)", "ogr")
        if not lyr.isValid():
            return None
        if QgsWkbTypes.geometryType(lyr.wkbType()) != QgsWkbTypes.PolygonGeometry:
            return None
        f = lyr.fields()
        if f.indexFromName("name") == -1 or f.indexFromName("gb") == -1:
            return None
        return lyr

    def _load_admin_layer_from_gpkg(self, gpkg_path):
        reg = QgsProviderRegistry.instance()
        details = []
        try:
            details = reg.querySublayers(gpkg_path)
        except Exception:
            details = []

        if not details:
            try:
                md = reg.providerMetadata("ogr")
                details = md.querySublayers(gpkg_path)
            except Exception:
                details = []

        for d in details:
            try:
                uri = d.uri() if callable(getattr(d, "uri", None)) else getattr(d, "uri", "")
                provider_key = d.providerKey() if callable(getattr(d, "providerKey", None)) else getattr(d, "providerKey", "ogr")
                if not provider_key:
                    provider_key = "ogr"
            except Exception:
                continue

            if not uri:
                continue

            lyr = QgsVectorLayer(uri, "行政区划(插件)", provider_key)
            if not lyr.isValid():
                continue
            if QgsWkbTypes.geometryType(lyr.wkbType()) != QgsWkbTypes.PolygonGeometry:
                continue
            f = lyr.fields()
            if f.indexFromName("name") == -1 or f.indexFromName("gb") == -1:
                continue
            return lyr

        fallback_names = ["XZQH", "xzqh", "admin", "layer"]
        for n in fallback_names:
            uri = f"{gpkg_path}|layername={n}"
            lyr = QgsVectorLayer(uri, "行政区划(插件)", "ogr")
            if not lyr.isValid():
                continue
            if QgsWkbTypes.geometryType(lyr.wkbType()) != QgsWkbTypes.PolygonGeometry:
                continue
            f = lyr.fields()
            if f.indexFromName("name") == -1 or f.indexFromName("gb") == -1:
                continue
            return lyr

        lyr = QgsVectorLayer(gpkg_path, "行政区划(插件)", "ogr")
        if not lyr.isValid():
            return None
        if QgsWkbTypes.geometryType(lyr.wkbType()) != QgsWkbTypes.PolygonGeometry:
            return None
        f = lyr.fields()
        if f.indexFromName("name") == -1 or f.indexFromName("gb") == -1:
            return None
        return lyr

    def _admin_layer_timestamp(self, admin_layer):
        if admin_layer and self._admin_shp_layer and admin_layer == self._admin_shp_layer:
            try:
                return os.path.getmtime(self._admin_shp_path)
            except Exception:
                return None
        try:
            return admin_layer.dataProvider().dataTimestamp()
        except Exception:
            return None

    def _get_admin_index(self, admin_layer):
        cache = self._admin_index_cache.get(admin_layer.id())
        layer_mtime = None
        layer_mtime = self._admin_layer_timestamp(admin_layer)
        if cache and cache.get("layer_mtime") == layer_mtime:
            return cache

        index = QgsSpatialIndex()
        req = QgsFeatureRequest()
        req.setNoAttributes()
        for f in admin_layer.getFeatures(req):
            index.addFeature(f)

        cache = {
            "index": index,
            "crs": admin_layer.crs(),
            "layer_mtime": layer_mtime,
        }
        self._admin_index_cache[admin_layer.id()] = cache
        return cache

    def _admin_fields_to_copy(self, target_layer, admin_layer):
        configured = self._settings.value("AutoSavePlugin/admin_copy_fields", "", type=str).strip()
        if configured:
            names = [x.strip() for x in configured.split(",") if x.strip()]
            return [n for n in names if target_layer.fields().indexFromName(n) != -1 and admin_layer.fields().indexFromName(n) != -1]

        candidates = [
            "province", "city", "county", "district", "town", "village",
            "prov", "pref", "name", "NAME", "NAME_1", "NAME_2", "NAME_3",
            "省", "市", "县", "区", "州", "盟", "乡", "镇", "街道", "村", "名称", "行政区",
            "gb", "GB", "gb_code", "GB_CODE",
            "adcode", "ad_code", "code", "CODE",
        ]
        out = []
        tf = target_layer.fields()
        af = admin_layer.fields()
        for n in candidates:
            if tf.indexFromName(n) != -1 and af.indexFromName(n) != -1:
                out.append(n)
        return out[:12]

    def _admin_field_mappings(self, target_layer, admin_layer):
        configured = self._settings.value("AutoSavePlugin/admin_field_map", "", type=str).strip()
        out = []

        if configured:
            parts = [x.strip() for x in configured.split(",") if x.strip()]
            for p in parts:
                if "=" not in p:
                    continue
                target_field, admin_field = [x.strip() for x in p.split("=", 1)]
                if not target_field or not admin_field:
                    continue
                if target_layer.fields().indexFromName(target_field) == -1:
                    continue
                if admin_layer.fields().indexFromName(admin_field) == -1:
                    continue
                out.append((target_field, admin_field))
            return out

        if target_layer.fields().indexFromName("region_cod") != -1 and admin_layer.fields().indexFromName("gb") != -1:
            out.append(("region_cod", "gb"))
        if target_layer.fields().indexFromName("XZQH") != -1 and admin_layer.fields().indexFromName("name") != -1:
            out.append(("XZQH", "name"))
        if target_layer.fields().indexFromName("xzqh") != -1 and admin_layer.fields().indexFromName("name") != -1:
            out.append(("xzqh", "name"))

        for n in self._admin_fields_to_copy(target_layer, admin_layer):
            out.append((n, n))

        seen = set()
        uniq = []
        for t, a in out:
            k = (t, a)
            if k in seen:
                continue
            seen.add(k)
            uniq.append(k)
        return uniq

    def _match_admin_feature(self, target_geom, target_crs, admin_layer, admin_cache):
        if target_geom is None or target_geom.isNull() or target_geom.isEmpty():
            return None

        pt = None
        try:
            c = target_geom.centroid()
            if not c.isNull() and not c.isEmpty():
                pt = c.asPoint()
        except Exception:
            pt = None

        if pt is None:
            try:
                b = target_geom.boundingBox()
                pt = b.center()
            except Exception:
                return None

        try:
            geom_pt = QgsGeometry.fromPointXY(pt)
        except Exception:
            return None

        if target_crs != admin_cache["crs"]:
            try:
                tr = QgsCoordinateTransform(target_crs, admin_cache["crs"], QgsProject.instance())
                geom_pt.transform(tr)
            except Exception:
                return None

        bbox = geom_pt.boundingBox()
        cand_ids = admin_cache["index"].intersects(bbox)
        if not cand_ids:
            return None

        req = QgsFeatureRequest().setFilterFids(cand_ids)
        for af in admin_layer.getFeatures(req):
            try:
                if af.geometry().contains(geom_pt):
                    return af
            except Exception:
                continue

        return None

    def update_admin_fields_for_modified_features(self, layer):
        if not isinstance(layer, QgsVectorLayer):
            return
        if QgsWkbTypes.geometryType(layer.wkbType()) != QgsWkbTypes.PolygonGeometry:
            return

        admin_layer = self._find_admin_layer()
        if not admin_layer or not isinstance(admin_layer, QgsVectorLayer):
            if not self._admin_warned_no_layer:
                self._admin_warned_no_layer = True
                self.log("未找到行政区划图层：不会进行空间匹配（可在设置 AutoSavePlugin/admin_layer_id 或关键词 AutoSavePlugin/admin_layer_keywords 指定）", Qgis.Warning)
            return

        if admin_layer.id() == layer.id():
            return

        admin_cache = self._get_admin_index(admin_layer)
        mappings = self._admin_field_mappings(layer, admin_layer)
        if not mappings:
            return

        edit_buffer = layer.editBuffer()
        if not edit_buffer:
            return

        try:
            added_fids = list(edit_buffer.addedFeatures().keys())
        except Exception:
            try:
                added_fids = list(edit_buffer.addedFeatures())
            except Exception:
                added_fids = []

        changed_geom_fids = list(edit_buffer.changedGeometries().keys())
        fids = sorted(set(added_fids) | set(changed_geom_fids))
        if not fids:
            return

        for fid in fids[:500]:
            feat = layer.getFeature(fid)
            if not feat.isValid():
                continue
            af = self._match_admin_feature(feat.geometry(), layer.crs(), admin_layer, admin_cache)
            if af is None or not af.isValid():
                self.log("行政区划匹配失败：图层='%s' fid=%s" % (layer.name(), self._safe_str(fid)), Qgis.Warning)
                continue

            for target_field, admin_field in mappings:
                try:
                    v = af[admin_field]
                except Exception:
                    continue
                idx = layer.fields().indexFromName(target_field)
                if idx == -1:
                    continue
                layer.changeAttributeValue(fid, idx, v)

    def initGui(self):
        # 创建菜单动作
        self.action = QAction("自动保存控制", self.iface.mainWindow())
        self.action.setCheckable(True)
        self.action.setChecked(True)
        self.action.triggered.connect(self.toggle_auto_save)
        self.iface.addPluginToMenu("&自动保存插件", self.action)

        self.action_settings = QAction("设置自动保存延迟…", self.iface.mainWindow())
        self.action_settings.triggered.connect(self.open_delay_settings)
        self.iface.addPluginToMenu("&自动保存插件", self.action_settings)

        # 为当前所有图层启用自动保存
        self.enable_for_all_current_layers()

        # 监听图层添加/移除
        project = QgsProject.instance()
        project.layersAdded.connect(self.on_layers_added)
        project.layersRemoved.connect(self.on_layers_removed)

        self.log("自动保存插件已启动（无中断模式），延迟 %d 毫秒" % self.delay_ms)
        self.log("提示：自动保存不会退出编辑模式或中断当前工具")

    def unload(self):
        # 停止所有定时器
        for timer in self._commit_timers.values():
            if timer.isActive():
                timer.stop()
        self._commit_timers.clear()

        # 断开图层信号（逐个禁用自动保存）
        for layer in list(self._auto_save_enabled):
            self.disable_auto_save(layer)

        # 断开工程信号
        project = QgsProject.instance()
        try:
            project.layersAdded.disconnect(self.on_layers_added)
            project.layersRemoved.disconnect(self.on_layers_removed)
        except TypeError:
            pass

        # 移除菜单
        if self.action:
            self.iface.removePluginMenu("&自动保存插件", self.action)
        if self.action_settings:
            self.iface.removePluginMenu("&自动保存插件", self.action_settings)

        self.log("自动保存插件已卸载")

    def open_delay_settings(self):
        dlg = DelaySettingDialog(self.delay_ms, self.iface.mainWindow())
        if dlg.exec() == QDialog.DialogCode.Accepted:
            self.delay_ms = int(dlg.value_ms())
            self._settings.setValue("AutoSavePlugin/delay_ms", self.delay_ms)
            self.log("已设置自动保存延迟 %d 毫秒" % self.delay_ms)

    def toggle_auto_save(self, checked):
        if checked:
            self.enable_for_all_current_layers()
            self.log("自动保存已重新启用")
        else:
            for layer in list(self._auto_save_enabled):
                self.disable_auto_save(layer)
            self.log("自动保存已手动禁用")

    def on_layers_added(self, layers):
        for layer in layers:
            if isinstance(layer, QgsVectorLayer):
                self.enable_auto_save(layer)

    def on_layers_removed(self, layer_ids):
        to_remove = [l for l in self._auto_save_enabled if l.id() in layer_ids]
        for layer in to_remove:
            self.disable_auto_save(layer)

    def enable_auto_save(self, layer):
        if not isinstance(layer, QgsVectorLayer):
            return
        if layer in self._auto_save_enabled:
            return

        # 使用 partial 生成可断开的具体回调函数
        callback = partial(self.schedule_commit, layer)
        # 保存回调引用，以便禁用时准确断开
        layer._autosave_callback = callback

        layer.featureAdded.connect(callback)
        layer.featureDeleted.connect(callback)
        layer.geometryChanged.connect(callback)
        layer.attributeValueChanged.connect(callback)

        self._auto_save_enabled.add(layer)
        self.log("已为图层 '%s' 启用自动保存" % layer.name())

    def disable_auto_save(self, layer):
        if layer not in self._auto_save_enabled:
            return

        # 获取保存的回调并断开
        callback = getattr(layer, "_autosave_callback", None)
        if callback:
            try:
                layer.featureAdded.disconnect(callback)
                layer.featureDeleted.disconnect(callback)
                layer.geometryChanged.disconnect(callback)
                layer.attributeValueChanged.disconnect(callback)
            except TypeError:
                pass
            delattr(layer, "_autosave_callback")

        # 停止并移除定时器
        if layer in self._commit_timers:
            timer = self._commit_timers[layer]
            if timer.isActive():
                timer.stop()
            del self._commit_timers[layer]

        self._auto_save_enabled.discard(layer)
        self.log("已为图层 '%s' 禁用自动保存" % layer.name())

    def schedule_commit(self, layer):
        """编辑操作后调用：重置定时器，延迟保存"""
        if layer not in self._auto_save_enabled:
            return
        if not layer.isEditable():
            return
        if self._saving:          # 正在保存中，忽略新触发的信号
            return

        # 停止该图层现有的定时器
        if layer in self._commit_timers:
            timer = self._commit_timers[layer]
            if timer.isActive():
                timer.stop()

        # 创建新定时器
        timer = QTimer()
        timer.setSingleShot(True)
        timer.timeout.connect(partial(self.do_commit, layer))
        self._commit_timers[layer] = timer
        timer.start(self.delay_ms)

    def do_commit(self, layer):
        """执行自动保存（无中断，不退出编辑模式）"""
        if self._saving:
            return
        if layer not in self._auto_save_enabled:
            return
        if not layer.isEditable():
            return
        if not layer.isModified():
            return

        self._saving = True
        try:
            self.log("执行自动保存：图层 '%s'" % layer.name())
            self.update_all_modified_geometries(layer)
            if not self._precheck_invalid_geometries(layer):
                return
            self.update_admin_fields_for_modified_features(layer)

            # 保存前标记并尝试保存
            # commitChanges(s) 在成功时会自动清除编辑缓冲区
            was_editable = layer.isEditable()
            success = layer.commitChanges(False)      # 关键修改：stopEditing=False 确保不退出编辑模式

            if not success:
                # 获取错误信息的方式也改变了
                errors = layer.commitErrors() if hasattr(layer, 'commitErrors') else "未知错误"
                self.log("保存失败：%s" % self._safe_str(errors), Qgis.Critical)
                self._log_commit_failure_details(layer, errors)
                self.iface.messageBar().pushMessage(
                    "自动保存",
                    "图层 '{}' 保存失败，请手动检查".format(layer.name()),
                    level=Qgis.Critical,
                    duration=5
                )
                # 如果保存失败且退出了编辑模式，重新进入
                if was_editable and not layer.isEditable():
                    layer.startEditing()
            else:
                self.log("保存成功：图层 '%s'" % layer.name())
                # 如果传入 stopEditing=True 导致退出了编辑模式，重新进入，确保连续编辑
                if was_editable and not layer.isEditable():
                    layer.startEditing()

        finally:
            self._saving = False
            if layer in self._commit_timers:
                del self._commit_timers[layer]

    def enable_for_all_current_layers(self):
        layers = QgsProject.instance().mapLayers().values()
        count = 0
        for layer in layers:
            if isinstance(layer, QgsVectorLayer):
                self.enable_auto_save(layer)
                count += 1
        self.log("已为 %d 个现有矢量图层启用自动保存" % count)

    # ------------------------- 面积计算部分（优化） -------------------------
    def get_utm_crs(self, longitude):
        zone = int((longitude + 180) / 6) + 1
        zone = max(1, min(60, zone))
        return QgsCoordinateReferenceSystem("EPSG:%d" % (32600 + zone))

    def calculate_area_in_meters(self, geom, source_crs):
        if geom.isNull() or geom.isEmpty():
            return 0.0
        centroid = geom.centroid()
        if centroid.isNull():
            bbox = geom.boundingBox()
            lon = (bbox.xMinimum() + bbox.xMaximum()) / 2.0
        else:
            if source_crs.isGeographic():
                lon = centroid.asPoint().x()
            else:
                wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")
                if source_crs != wgs84:
                    trans = QgsCoordinateTransform(source_crs, wgs84, QgsProject.instance())
                    centroid_geo = trans.transform(centroid)
                    lon = centroid_geo.asPoint().x()
                else:
                    lon = centroid.asPoint().x()
        utm_crs = self.get_utm_crs(lon)
        transform = QgsCoordinateTransform(source_crs, utm_crs, QgsProject.instance())
        try:
            geom_utm = geom
            geom_utm.transform(transform)
            return geom_utm.area()
        except Exception as e:
            self.log("面积计算失败: %s" % str(e), Qgis.Critical)
            return 0.0

    def find_area_field(self, layer):
        candidates = ['area_sqm', 'area_m2', 'area', '面积', 'sqm']
        fields = layer.fields()
        for name in candidates:
            idx = fields.indexFromName(name)
            if idx != -1:
                return idx
        # 没有匹配名称，返回第一个数值型字段（且不是整型时警告）
        for i in range(fields.count()):
            if fields[i].isNumeric():
                if fields[i].type() not in (6, 8, 10):
                    self.log("警告：面积字段 '%s' 可能为整型，会丢失小数" % fields[i].name(), Qgis.Warning)
                return i
        return -1

    def update_area_for_feature(self, layer, fid, geom, field_idx):
        area_m2 = self.calculate_area_in_meters(geom, layer.crs())
        if area_m2 > 0:
            layer.changeAttributeValue(fid, field_idx, area_m2)

    def update_all_modified_geometries(self, layer):
        """遍历编辑缓冲区中新增或修改的要素，更新面积"""
        field_idx = self.find_area_field(layer)
        if field_idx == -1:
            return

        edit_buffer = layer.editBuffer()
        if not edit_buffer:
            return

        # 更新几何被修改的要素
        for fid, geom in edit_buffer.changedGeometries().items():
            self.update_area_for_feature(layer, fid, geom, field_idx)

        # 更新新增的要素
        for fid in edit_buffer.addedFeatures():
            feat = layer.getFeature(fid)
            if feat.isValid():
                self.update_area_for_feature(layer, fid, feat.geometry(), field_idx)
