# -*- coding: utf-8 -*-
"""Gap detector — dissolve all polygons, find interior rings (holes).

Each interior ring in the dissolved geometry represents a gap between
the original polygons. Gap detection uses area threshold:
- Gaps with area <= gap_tolerance are detected as errors
  (small gaps that should be filled)

Author: Loc Vu Trung tong hop vi dam me cung ban!
"""
from .base import BaseChecker


class GapChecker(BaseChecker):
    name = "gaps"
    display_name_en = "Gaps"
    display_name_vi = "Khoang ho (Gaps)"

    def check(self, layer, params):
        from qgis.core import QgsGeometry
        # gap_tolerance in ha, convert to m²
        gap_tol = params.get("gap_tolerance", 0.001) * 10000
        all_geoms = [f.geometry() for f in layer.getFeatures()
                     if f.geometry() and not f.geometry().isNull()]
        if not all_geoms:
            return {"error_geometries": [], "error_features": [], "details": []}

        dissolved = QgsGeometry.unaryUnion(all_geoms)
        gaps = []

        def extract_gaps(poly_rings):
            # poly_rings[0] = exterior ring
            # poly_rings[1:] = interior rings = gaps
            for ring_idx in range(1, len(poly_rings)):
                gap_geom = QgsGeometry.fromPolygonXY([poly_rings[ring_idx]])
                area = gap_geom.area()
                if area > 0:
                    # gap_tolerance = minimum area threshold
                    # Gaps smaller than tolerance = noise, skip
                    # Gaps larger than tolerance = real errors
                    if gap_tol <= 0 or area > gap_tol:
                        gaps.append(gap_geom)

        if dissolved.isMultipart():
            for part in dissolved.asMultiPolygon():
                extract_gaps(part)
        else:
            poly = dissolved.asPolygon()
            if poly:
                extract_gaps(poly)

        return {"error_geometries": gaps, "error_features": [],
                "details": [f"Gap: {g.area():.4f} m2" for g in gaps]}

    def fix(self, input_path, output_path, params):
        from qgis.core import (
            QgsVectorLayer, QgsSpatialIndex,
            QgsVectorFileWriter, QgsCoordinateTransformContext)
        layer = QgsVectorLayer(input_path, "in", "ogr")
        gaps = self.check(layer, params)["error_geometries"]
        if not gaps:
            opts = QgsVectorFileWriter.SaveVectorOptions()
            opts.driverName = "GPKG"
            opts.forceMulti = True
            opts.includeZ = True
            QgsVectorFileWriter.writeAsVectorFormatV3(
                layer, output_path, QgsCoordinateTransformContext(), opts)
            return output_path

        index = QgsSpatialIndex(layer.getFeatures())
        features = {f.id(): f for f in layer.getFeatures()}
        changes = {}

        for gap in gaps:
            best_fid, best_len = None, 0
            for fid in index.intersects(gap.boundingBox()):
                feat = features[fid]
                g = changes.get(fid, feat.geometry())
                shared = g.intersection(gap.boundary())
                slen = shared.length() if shared and not shared.isNull() else 0
                if slen > best_len:
                    best_len = slen
                    best_fid = fid
            if best_fid is not None:
                base = changes.get(best_fid, features[best_fid].geometry())
                merged = base.combine(gap)
                if merged and not merged.isNull():
                    changes[best_fid] = merged

        if changes:
            layer.dataProvider().changeGeometryValues(changes)

        opts = QgsVectorFileWriter.SaveVectorOptions()
        opts.driverName = "GPKG"
        opts.forceMulti = True
        opts.includeZ = True
        QgsVectorFileWriter.writeAsVectorFormatV3(
            layer, output_path, QgsCoordinateTransformContext(), opts)
        return output_path
