# -*- coding: utf-8 -*-
"""
/***************************************************************************
 walidatorPlikowGML
                                 A QGIS plugin
 Walidator plików GML baz BDOT10k, PRNG, GESUT, EGiB, BDOT500
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2022-12-23
        git sha              : $Format:%H$
        copyright            : (C) 2022 by Marcin Lebiecki - Główny Urząd Geodezji i Kartografii
        email                : marcin.lebiecki@gugik.gov.pl
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from qgis.core import *
from qgis.PyQt.QtCore import *
from qgis import processing
from qgis.PyQt.QtWidgets import *
import pathlib
import configparser
import copy
import math
import unicodedata
import sys
import re
from datetime import datetime
import lxml
from lxml import etree
import pandas as pd
import zipfile
import os
from qgis.core import QgsMessageLog, QgsProject, QgsSpatialIndex
from collections import defaultdict, Counter
from math import acos, degrees, sqrt



loaded_csv_data = None
loaded_gml_prng_miejscowosci = None
loaded_gml_prng_obiektyfizjograficzne = None
tereny_chronione_zip = None
loaded_shp = {'OT_TCPN_A':[],'OT_TCPK_A':[],'OT_TCRZ_A':[],'OT_TCON_A':[]}


def findDuplicates(layer):
    obiektyZbledami = []
    
    extractbyexpression = processing.run("native:extractbyexpression", {
        'INPUT': layer,
        'EXPRESSION': '"rodzaj" != \'punkt wysokościowy w terenie\'',
        'OUTPUT': 'memory:'
    })
    if layer.geometryType() == QgsWkbTypes.LineGeometry:
        obj_dl1 = {}
        for obj in extractbyexpression['OUTPUT'].getFeatures():
            dl = obj.geometry().length()
            if dl not in obj_dl1.keys():
                obj_dl1[dl] = obj
            else:
                if obj_dl1[dl].geometry().equals(obj.geometry()):
                    obiektyZbledami.append(obj)
    else:
        def localFun(obiektyZbledami, lyr):
            deleteduplicategeometries = processing.run("native:deleteduplicategeometries", {
                'INPUT': lyr,
                'OUTPUT': 'memory:'
            })
            liczbaUsunietychObiektow = lyr.featureCount() - deleteduplicategeometries['OUTPUT'].featureCount()
            if liczbaUsunietychObiektow > 0:
                for obj1 in lyr.getFeatures():
                    czyObiektIstnieje = False
                    for obj2 in deleteduplicategeometries['OUTPUT'].getFeatures():
                        if obj1['gml_id'] == obj2['gml_id']:
                            czyObiektIstnieje = True
                            break
                    if not czyObiektIstnieje:
                        obiektyZbledami.append(obj1)
        localFun(obiektyZbledami, extractbyexpression['OUTPUT'])

    return obiektyZbledami


def findDuplicatesOT(layer):
    """
    Wykrywa duplikaty obiektów OT. 
    Dla OT_ADJA/ADMS/ADMS_P i OT_TCON_A ignoruje obiekty z identyczną geometrią, 
    jeśli różnią się wartościami wybranych atrybutów ('rodzaj' dla AD*, 'numerCRFOP' dla TCON).
    """
    obiektyZbledami = []
    try:
        if not layer or not layer.isValid():
            return obiektyZbledami
        nazwa = layer.name()
        if nazwa.endswith(('RTLW_L', 'RTPW_P')):
            return obiektyZbledami
        extract = processing.run(
            "native:extractbyexpression",
            {'INPUT': layer, 'EXPRESSION': '1=1', 'OUTPUT': 'memory:'}
        )
        input_layer = extract['OUTPUT']
        if nazwa.endswith(('ADJA_A', 'ADMS_A', 'ADMS_P', 'OT_TCON_A')):
            geom_map = {}
            for f in input_layer.getFeatures():
                geom = f.geometry()
                if not geom or geom.isEmpty():
                    continue
                geom_wkt = geom.asWkt()
                geom_map.setdefault(geom_wkt, []).append(f)
            for feats in geom_map.values():
                if len(feats) > 1:
                    attr_map = {}
                    for f in feats:
                        key_attr = str(f['rodzaj'] or '').strip() if nazwa.endswith(('ADJA_A', 'ADMS_A', 'ADMS_P')) else str(f['numerCRFOP'] or '').strip()
                        attr_map.setdefault(key_attr, []).append(f)
                    for group in attr_map.values():
                        if len(group) > 1:
                            obiektyZbledami.append(group[0])
        else:
            delete_dupes = processing.run(
                "native:deleteduplicategeometries",
                {'INPUT': input_layer, 'OUTPUT': 'memory:'}
            )
            wynik_layer = delete_dupes['OUTPUT']
            kept_ids = {f['gml_id'] for f in wynik_layer.getFeatures()}
            potencjalne_duplikaty = [f for f in input_layer.getFeatures() if f['gml_id'] not in kept_ids]
            seen = set()
            for f in potencjalne_duplikaty:
                if f['gml_id'] not in seen:
                    obiektyZbledami.append(f)
                    seen.add(f['gml_id'])
    except Exception as e:
        print(f"Błąd w kontroli findDuplicatesOT: {e}")
        obiektyZbledami = []

    return obiektyZbledami


def validateGeometryRTLW(layer):
    obiektyZbledami = []
    def has_duplicate_vertices(pts, closed):
        seen = set()
        limit = len(pts)-1 if closed else len(pts)
        for i in range(limit):
            p = (pts[i].x(), pts[i].y())
            if p in seen: return True
            seen.add(p)
        return False
    def seg_intersect(a,b,c,d):
        def orient(p,q,r):
            return (q.x()-p.x())*(r.y()-p.y()) - (q.y()-p.y())*(r.x()-p.x())
        o1 = orient(a,b,c)
        o2 = orient(a,b,d)
        o3 = orient(c,d,a)
        o4 = orient(c,d,b)
        return o1*o2 < 0 and o3*o4 < 0
    def has_self_intersection(pts, closed):
        segs = [(pts[i], pts[i+1], i) for i in range(len(pts)-1)]
        grid = defaultdict(list)
        cell = 50
        for a,b,i in segs:
            cx = int((a.x()+b.x())//2//cell)
            cy = int((a.y()+b.y())//2//cell)
            grid[(cx,cy)].append((a,b,i))
        for bucket in grid.values():
            n = len(bucket)
            for i in range(n):
                a1,b1,i1 = bucket[i]
                for j in range(i+1,n):
                    a2,b2,i2 = bucket[j]
                    if abs(i1-i2)<=1: continue
                    if closed and {i1,i2}=={0,len(pts)-2}: continue
                    if seg_intersect(a1,b1,a2,b2):
                        return True
        return False
    for f in layer.getFeatures():
        g = f.geometry()
        if not g or g.type()!=QgsWkbTypes.LineGeometry:
            continue
        lines = g.asMultiPolyline() if g.isMultipart() else [g.asPolyline()]
        for pts in lines:
            if len(pts)<4: continue
            closed = pts[0]==pts[-1]
            if has_duplicate_vertices(pts, closed):
                obiektyZbledami.append(f); break
            if has_self_intersection(pts, closed):
                obiektyZbledami.append(f); break

    return obiektyZbledami



lokalizator_bledow = None
def validateGeometry(layer):
    """Kontrola geometrii: checkValidity, zdublowane wierzchołki, samoprzecięcia linii."""
    global lokalizator_bledow
    obiektyZbledami = []
    def get_attr(f, name, default=""):
        try: return f[name]
        except: return default
    def extract_coords(geom):
        if geom.isEmpty(): return []
        t = geom.type()
        if t == QgsWkbTypes.PointGeometry:
            return [[(geom.asPoint().x(), geom.asPoint().y())]]
        elif t == QgsWkbTypes.LineGeometry:
            lines = geom.asMultiPolyline() if geom.isMultipart() else [geom.asPolyline()]
            return [[(pt.x(), pt.y()) for pt in line] for line in lines]
        elif t == QgsWkbTypes.PolygonGeometry:
            polys = geom.asMultiPolygon() if geom.isMultipart() else [geom.asPolygon()]
            return [[(pt.x(), pt.y()) for pt in ring] for poly in polys for ring in poly]
        return []
    def find_adjacent_duplicates(coords_list):
        dup = set()
        for ring in coords_list:
            for i in range(len(ring)-1):
                if round(ring[i][0],2) == round(ring[i+1][0],2) and round(ring[i][1],2) == round(ring[i+1][1],2):
                    dup.add((round(ring[i][0],2), round(ring[i][1],2)))
        return list(dup)
    def seg_intersection(p1,p2,p3,p4):
        x1,y1=p1; x2,y2=p2; x3,y3=p3; x4,y4=p4
        denom=(x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
        if denom == 0: return None
        px=((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/denom
        py=((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/denom
        return (px,py)
    def line_self_intersection(coords, tol=0.01):
        n = len(coords)
        if n < 4: return False, None
        def ccw(ax,ay,bx,by,cx,cy):
            return (cy-ay)*(bx-ax) > (by-ay)*(cx-ax)
        def same_point(a,b):
            return abs(a[0]-b[0])<=tol and abs(a[1]-b[1])<=tol
        for i in range(n-1):
            for j in range(i+2, n-1):
                if j == i+1: continue
                p1,p2 = coords[i], coords[i+1]
                p3,p4 = coords[j], coords[j+1]
                if ccw(*p1,*p3,*p4)!=ccw(*p2,*p3,*p4) and ccw(*p1,*p2,*p3)!=ccw(*p1,*p2,*p4):
                    pt = seg_intersection(p1,p2,p3,p4)
                    if not pt: continue
                    if (same_point(pt,p1) or same_point(pt,p2) or
                        same_point(pt,p3) or same_point(pt,p4)):
                        continue
                    return True, pt
        return False, None
    def is_closed_like_circle(coords, tol=0.1):
        if not coords: return False
        x0,y0=coords[0]; x1,y1=coords[-1]
        return math.sqrt((x1-x0)**2 + (y1-y0)**2) <= tol
    isvalid = processing.run("qgis:checkvalidity", {
        'INPUT_LAYER':layer,'METHOD':2,
        'VALID_OUTPUT':'memory:','INVALID_OUTPUT':'memory:','ERROR_OUTPUT':'memory:'
    })
    dp = None
    if lokalizator_bledow is None:
        lokalizator_bledow = QgsVectorLayer("Point?crs=EPSG:2180", "lokalizator_bledow_geometrii", "memory")
        dp = lokalizator_bledow.dataProvider()
        dp.addAttributes([
            QgsField("gml_id", QVariant.String),
            QgsField("lokalnyId", QVariant.String),
            QgsField("warstwa", QVariant.String),
            QgsField("trescBledu", QVariant.String)
        ])
        lokalizator_bledow.updateFields()
    else:
        dp = lokalizator_bledow.dataProvider()
    layer.startEditing()
    for f in layer.getFeatures():
        geom = f.geometry()
        if not geom or geom.isEmpty(): continue
        coords_list = extract_coords(geom)
        lista_bledow = []
        lokalizatory = set()
        if geom.type() == QgsWkbTypes.PolygonGeometry and not geom.isGeosValid():
            lista_bledow.append("samoprzecięcie pierścienia")
        dup = find_adjacent_duplicates(coords_list)
        if dup:
            lista_bledow.append("zdublowane wierzchołki")
            lokalizatory = {(d[0], d[1]) for d in dup}
        if geom.type() == QgsWkbTypes.LineGeometry:
            for line in coords_list:
                if not line or len(line)<=3 or is_closed_like_circle(line): continue
                inter, pt = line_self_intersection(line)
                if inter:
                    lista_bledow.append("samoprzecięcie linii")
                    if pt: lokalizatory.add((round(pt[0],2), round(pt[1],2)))
        if lista_bledow:
            gml_id = get_attr(f,"gml_id")
            lokalnyId = get_attr(f,"lokalnyId","")
            tresc = " | ".join(lista_bledow)
            obiektyZbledami.append(f)
            for pt in lokalizatory:
                feat = QgsFeature(lokalizator_bledow.fields())
                feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(pt[0], pt[1])))
                feat.setAttributes([gml_id, lokalnyId, layer.name(), tresc])
                dp.addFeature(feat)
    layer.commitChanges()
    layer.triggerRepaint()
    if dp is not None:
        for f in isvalid['ERROR_OUTPUT'].getFeatures():
            geom = f.geometry()
            if not geom or geom.isEmpty(): continue
            coords_list = extract_coords(geom)

            for ring in coords_list:
                for i in range(len(ring)-1):
                    for j in range(i+2, len(ring)-1):
                        p1=ring[i]; p2=ring[i+1]
                        p3=ring[j]; p4=ring[j+1]
                        pt = seg_intersection(p1,p2,p3,p4)
                        if pt:
                            feat = QgsFeature(lokalizator_bledow.fields())
                            feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(round(pt[0],2), round(pt[1],2))))
                            feat.setAttributes([None, None, layer.name(), "samoprzecięcie pierścienia"])
                            dp.addFeature(feat)

    if lokalizator_bledow and dp.featureCount()>0 and lokalizator_bledow not in QgsProject.instance().mapLayers().values():
        lokalizator_bledow.updateExtents()
        QgsProject.instance().addMapLayer(lokalizator_bledow)

    return obiektyZbledami



def adjaMinus2cmBufor(layer):
    config = configparser.ConfigParser()
    mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath()) / pathlib.Path(r"python/plugins/Walidator_plikow_gml/")
    config.read(str(mainPath) + '/Walidator_plikow_gml.ini')
    granicePowiatowPath = config['DEFAULT']['granicepowiatow']
    granicePowiatow = QgsVectorLayer(granicePowiatowPath, 'GranicePowiatow', 'ogr')
    if not granicePowiatow.isValid():
        return QgsVectorLayer("LineString?crs=epsg:2180", "pusta", "memory")
    granicePowiatow = processing.run("native:multiparttosingleparts", {
        'INPUT': granicePowiatow,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    match = re.search(r"\.(\d{4})__", layer.name())
    if not match:
        return QgsVectorLayer("LineString?crs=epsg:2180", "pusta", "memory")
    terytPowiatu = match.group(1)
    powiat_z_PRG = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "granica powiatu z PRG", "memory")
    dopasowane = False
    for obj in granicePowiatow.getFeatures():
        if str(obj['JPT_KOD_JE']) == str(terytPowiatu):
            powiat_z_PRG.dataProvider().addFeatures([obj])
            dopasowane = True
            break
    if not dopasowane:
        print(f"Nie znaleziono powiatu o kodzie {terytPowiatu}.")
        return QgsVectorLayer("LineString?crs=epsg:2180", "pusta", "memory")
    bufor = processing.run("native:buffer", {
        'INPUT': powiat_z_PRG, 
        'DISTANCE': -0.02,
        'SEGMENTS': 10,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    granica = processing.run("native:polygonstolines", {
        'INPUT': bufor,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    return granica


def minimalnaPowierzchnia(layer):
    minPowWarstwy = {"OT_PTWP_A":80,"OT_PTLZ_A":500, "OT_PTRK_A":1000,"OT_PTUT_A":1000,"OT_PTGN_A":1000,
                     "OT_PTSO_A":1000,"OT_PTWZ_A":1000,"OT_PTNZ_A":1000,"OT_BUWT_A":100,"OT_BUZT_A":175,"OT_KUSK_A":3000,
                     "OT_KUHO_A":3000,"OT_KUHU_A":3000,"OT_KUOS_A":3000,"OT_KUOZ_A":3000,"OT_KUZA_A":3000}
    obiektyZbledami = []
    klasa = layer.name()[-9:]
    granica = adjaMinus2cmBufor(layer)
    for feature in layer.getFeatures():
        geom = feature.geometry()
        if klasa == 'OT_BUZT_A' and feature['rodzaj'] != 'zbiornik':
            continue
        else:
            if geom.area() < minPowWarstwy[klasa]:
                for g in granica.getFeatures():
                    if not geom.intersects(g.geometry()):
                        obiektyZbledami.append(feature)
    
    return obiektyZbledami


def minimalnaPTRKwzgledemPTLZ(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'PTRK_A':
        PTLZ_A_layer = QgsProject().instance().mapLayersByName(layer.name().replace("OT_PTRK_A","OT_PTLZ_A"))[0]
        PTRK_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    identyfikatory_bledow = set()
    granica = adjaMinus2cmBufor(layer)
    for PTRK_A_feature in PTRK_A_layer.getFeatures():
        for PTLZ_A_feature in PTLZ_A_layer.getFeatures():
            geom1 = PTRK_A_feature.geometry()
            geom2 = PTLZ_A_feature.geometry()
            if geom1.area() < 2000 and PTRK_A_feature.attribute('rodzaj') == 'krzewy':
                styka = False
                for g in granica.getFeatures():
                    if geom1.intersects(geom2) and not geom1.intersects(g.geometry()) and PTRK_A_feature.id() not in identyfikatory_bledow:
                        styka = True
                        obiektyZbledami.append(PTRK_A_feature)
                        identyfikatory_bledow.add(PTRK_A_feature.id())
    
    return obiektyZbledami


def minimalnaPTTRronda(layer):
    minimalnaPowierzchniaBezWod(layer)
    return obiektyZbledami


def minimalnaPowierzchniaBezWod(layer):
    obiektyZbledami = []
    minPowWarstwy = {"OT_PTGN_A": 1000, "OT_PTLZ_A": 500, "OT_PTRK_A": 1000, "OT_PTTR_A": 500, "OT_PTZB_A": 1000}
    klasa = layer.name()[-9:]
    granica = adjaMinus2cmBufor(layer)
    index_granica = QgsSpatialIndex()
    index_pokrycie = []
    for lyr in QgsProject.instance().mapLayers().values():
        if "OT_PT" in lyr.name():
            index = QgsSpatialIndex()
            features_cache = {f.id(): f for f in lyr.getFeatures()}
            for f in features_cache.values():
                index.insertFeature(f)
            index_pokrycie.append((lyr, index, features_cache))
    for g_feat in granica.getFeatures():
        index_granica.insertFeature(g_feat)
    if klasa == 'OT_PTTR_A':
        SKRW_P_layer = QgsProject.instance().mapLayersByName(layer.name().replace(klasa, "OT_SKRW_P"))[0]
        index_SKRW_P = QgsSpatialIndex()
        identyfikatory_bledow = set()
        for skrw_feat in SKRW_P_layer.getFeatures():
            if skrw_feat['rodzaj'] == 'rondo':
                index_SKRW_P.insertFeature(skrw_feat)
    for feature in layer.getFeatures():
        geom = feature.geometry()
        if klasa in minPowWarstwy and geom.area() > minPowWarstwy[klasa]:
            continue
        bbox = geom.boundingBox()
        graniczy_z = set()
        for lyr, index, features_cache in index_pokrycie:
            fids = index.intersects(bbox)
            for fid in fids:
                if fid == feature.id():
                    continue
                lyr_feat = features_cache[fid]
                if geom.intersects(lyr_feat.geometry()):
                    graniczy_z.add(lyr.name())
        if len(graniczy_z) == 1 and any(name.endswith('OT_PTWP_A') for name in graniczy_z):
            continue
        dodaj_do_bledow = False
        if klasa == 'OT_PTTR_A':
            fids_SKRW = index_SKRW_P.intersects(bbox)
            intersects_SKRW_P = any(geom.intersects(SKRW_P_layer.getFeature(fid).geometry()) for fid in fids_SKRW)
            fids_granica = index_granica.intersects(bbox)
            touches_boundary = any(geom.intersects(granica.getFeature(fid).geometry()) for fid in fids_granica)
            fids_own = index_pokrycie[0][1].intersects(bbox) 
            surrounded_by_water = True
            for fid in fids_own:
                otoczone = False
                for lyr, index, _ in index_pokrycie:
                    fids_check = index.intersects(bbox)
                    if any(lyr.name().endswith('OT_PTWP_A') for lyr, _, _ in index_pokrycie):
                        otoczone = True
                        break
                if not otoczone:
                    surrounded_by_water = False
                    break

            if not intersects_SKRW_P and not touches_boundary and not surrounded_by_water and feature.id() not in identyfikatory_bledow:
                dodaj_do_bledow = True
                identyfikatory_bledow.add(feature.id())
        else:
            if not hasattr(minimalnaPowierzchniaBezWod, '_granica_features'):
                minimalnaPowierzchniaBezWod._granica_features = list(granica.getFeatures())
            styka = any(geom.intersects(g.geometry()) for g in minimalnaPowierzchniaBezWod._granica_features)
            if not styka:
                dodaj_do_bledow = True
        if dodaj_do_bledow:
            if klasa == 'OT_PTZB_A':
                gml_id = feature.attribute("gml_id") or "NULL"
                info = f"{gml_id + ' | ' if gml_id != 'NULL' else ''} {geom.area():.2f} m2 - DO INTERPRETACJI"
                feature.setAttribute(layer.fields().indexFromName("gml_id"), info)
                layer.updateFeature(feature)
            obiektyZbledami.append(feature)

    return obiektyZbledami


def minimalnaPTPL(layer):
    global obiektyZbledami
    obiektyZbledami = []
    
    minPowPTPL = 1000
    
    granica = adjaMinus2cmBufor(layer)
    if granica is None or not granica.isValid():
        return obiektyZbledami
    
    index_granica = QgsSpatialIndex(granica.getFeatures())
    
    for feature in layer.getFeatures():
        # Pomijamy place, które mają nazwę
        if "placNazwa1" in feature.fields().names() and feature["placNazwa1"]:
            continue
        
        geom = feature.geometry()
        
        # Sprawdzenie minimalnej powierzchni
        if geom.area() < minPowPTPL:
            touches_boundary = any(
                geom.intersects(granica.getFeature(fid).geometry())
                for fid in index_granica.intersects(geom.boundingBox())
            )
            
            if not touches_boundary:
                obiektyZbledami.append(feature)
    
    return obiektyZbledami


def minimalnaBUIT(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'BUIT_A':
        BUIT_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    granica = adjaMinus2cmBufor(layer)
    for BUIT_A_feature in BUIT_A_layer.getFeatures():
        geom = BUIT_A_feature.geometry()
        if geom.area() < 1000 and BUIT_A_feature['rodzaj'] in ['zespół dystrybutorów paliwa', 'zespół transformatorów']:
            styka = False
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    styka = True
                    obiektyZbledami.append(BUIT_A_feature)
    
    return obiektyZbledami


def minimalnaKUPG(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'KUPG_A':
        KUPG_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    granica = adjaMinus2cmBufor(layer)
    for KUPG_A_feature in KUPG_A_layer.getFeatures():
        geom = KUPG_A_feature.geometry()
        if geom.area() < 3000 and KUPG_A_feature['rodzaj'] not in ['oczyszczalnia ścieków', 'podstacja elektroenergetyczna', 'teren ujęcia wody']:
            styka = False
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    styka = True
                    obiektyZbledami.append(KUPG_A_feature)
    
    return obiektyZbledami


def minimalnaKUKO(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'KUKO_A':
        KUKO_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    granica = adjaMinus2cmBufor(layer)
    for KUKO_A_feature in KUKO_A_layer.getFeatures():
        geom = KUKO_A_feature.geometry()
        if geom.area() < 3000 and KUKO_A_feature['rodzaj'] != 'stacja paliw':
            styka = False
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    styka = True
                    obiektyZbledami.append(KUKO_A_feature)
    
    return obiektyZbledami


def minimalnaKUSC(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'KUSC_A':
        KUSC_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    granica = adjaMinus2cmBufor(layer)
    for KUSC_A_feature in KUSC_A_layer.getFeatures():
        geom = KUSC_A_feature.geometry()
        if geom.area() < 5000 and KUSC_A_feature['rodzaj'] == 'zespół sakralny lub klasztorny':
            styka = False
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    styka = True
                    obiektyZbledami.append(KUSC_A_feature)
    
    return obiektyZbledami


def minimalnaOIKM(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'OIKM_A':
        OIKM_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    granica = adjaMinus2cmBufor(layer)
    for OIKM_A_feature in OIKM_A_layer.getFeatures():
        geom = OIKM_A_feature.geometry()
        if geom.area() < 500 and OIKM_A_feature['rodzaj'] == 'schody':
            styka = False
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    styka = True
                    obiektyZbledami.append(OIKM_A_feature)
    
    return obiektyZbledami


def minimalnaOIORschron(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'OIOR_A':
        OIOR_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    granica = adjaMinus2cmBufor(layer)
    for OIOR_A_feature in OIOR_A_layer.getFeatures():
        geom = OIOR_A_feature.geometry()
        if geom.area() < 100 and OIOR_A_feature['rodzaj'] == 'bunkier lub schron':
            styka = False
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    styka = True
                    obiektyZbledami.append(OIOR_A_feature)
    
    return obiektyZbledami


def minimalnaOIORszklarnia(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'OIOR_A':
        OIOR_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    granica = adjaMinus2cmBufor(layer)
    for OIOR_A_feature in OIOR_A_layer.getFeatures():
        geom = OIOR_A_feature.geometry()
        if geom.area() < 100 and OIOR_A_feature['rodzaj'] == 'szklarnia niebędąca budynkiem':
            styka = False
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    styka = True
                    obiektyZbledami.append(OIOR_A_feature)
    
    return obiektyZbledami


def minimalnaOIORwiataAltana(layer):
    obiektyZbledami = []
    if layer.name()[-6:] == 'OIOR_A':
        KUKO_A_layer = QgsProject().instance().mapLayersByName(layer.name().replace("OT_OIOR_A","OT_KUKO_A"))[0]
        OIOR_A_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
    identyfikatory_bledow = set()
    granica = adjaMinus2cmBufor(layer)
    for OIOR_A_feature in OIOR_A_layer.getFeatures():
        for KUKO_A_feature in KUKO_A_layer.getFeatures():
            geom1 = OIOR_A_feature.geometry()
            geom2 = KUKO_A_feature.geometry()
            if geom1.area() < 200 and OIOR_A_feature['rodzaj'] == 'wiata lub altana' or (geom1.intersects(geom2) and KUKO_A_feature['rodzaj'] == 'stacja paliw'): 
                styka = False
                for g in granica.getFeatures():
                    if not geom1.intersects(g.geometry()) and OIOR_A_feature.id() not in identyfikatory_bledow:
                        styka = True
                        obiektyZbledami.append(OIOR_A_feature)
                        identyfikatory_bledow.add(OIOR_A_feature.id())
    
    return obiektyZbledami


def minimalnaBUBD(layer):
    try:
        if not layer.name().endswith("OT_BUBD_A"):
            return []
        obiektyZbledami, already_marked = [], set()
        ptut_layer = next((l for l in QgsProject.instance().mapLayers().values() if l.name().endswith("OT_PTUT_A")), None)
        ptut_index, ptut_dict = QgsSpatialIndex(), {}
        if ptut_layer:
            for f in ptut_layer.getFeatures():
                if f["rodzaj"] == "ogródki działkowe":
                    geom = f.geometry()
                    if geom:
                        ptut_index.insertFeature(f)
                        ptut_dict[f.id()] = (f, geom)
        bubd_index, bubd_dict, geom_cache, area_cache, funkcja_cache = QgsSpatialIndex(), {}, {}, {}, {}
        excluded_funkcje = {
                "budynek kontroli ruchu kolejowego",
                "budynek kontroli ruchu powietrznego",
                "dom kultury",
                "dzwonnica",
                "kapitanat lub bosmanat portu",
                "latarnia morska",
                "młyn",
                "stacja gazowa",
                "stacja kolejki górskiej lub wyciągu krzesełkowego",
                "stacja pomp",
                "stacja transformatorowa",
                "straż pożarna",
                "toaleta publiczna",
                "wiatrak"
        }
        for f in layer.getFeatures():
            geom = f.geometry()
            if not geom:
                continue
            fid = f.id()
            bubd_index.insertFeature(f)
            bubd_dict[fid] = f
            geom_cache[fid] = geom
            area_cache[fid] = geom.area()
            funkcja_cache[fid] = (f["funkcjaOgolnaBudynku"], f["przewazajacaFunkcjaBudynku"])
        for fid, f in bubd_dict.items():
            funkcja_og, przew_funkcja = funkcja_cache[fid]
            if funkcja_og == "budynki mieszkalne" or przew_funkcja in excluded_funkcje:
                continue
            if fid in already_marked:
                continue
            geom, area = geom_cache[fid], area_cache[fid]
            if area >= 40:
                continue
            if ptut_layer:
                for cid in ptut_index.intersects(geom.boundingBox()):
                    ptut_f, ptut_geom = ptut_dict[cid]
                    if geom.within(ptut_geom):
                        obiektyZbledami.append(f)
                        already_marked.add(fid)
                        break
        for fid, f in bubd_dict.items():
            if fid in already_marked:
                continue
            funkcja_og, przew_funkcja = funkcja_cache[fid]
            if funkcja_og == "budynki mieszkalne" or przew_funkcja in excluded_funkcje:
                continue
            geom, area = geom_cache[fid], area_cache[fid]
            if area >= 16:
                continue
            touching = []
            for cid in bubd_index.intersects(geom.boundingBox()):
                if cid == fid:
                    continue
                geom2 = geom_cache[cid]
                if not geom.intersects(geom2):
                    continue
                inter = geom.intersection(geom2)
                wsp_dlugosc = inter.length() if inter and hasattr(inter, "length") else 0
                if wsp_dlugosc > 0.02:
                    touching.append(cid)
            if not touching:
                buf_ids = bubd_index.intersects(geom.buffer(50, 1).boundingBox())
                if any(area_cache[cid] > 40 for cid in buf_ids if cid != fid):
                    obiektyZbledami.append(f)
                    already_marked.add(fid)
            elif len(touching) == 1:
                sasiad_id = touching[0]
                if area_cache[sasiad_id] >= 40:
                    obiektyZbledami.append(f)
                    already_marked.add(fid)
        for fid, f in bubd_dict.items():
            if fid in already_marked:
                continue
            funkcja_og, przew_funkcja = funkcja_cache[fid]
            if funkcja_og == "budynki mieszkalne" or przew_funkcja in excluded_funkcje:
                continue
            geom, area = geom_cache[fid], area_cache[fid]
            if area >= 16:
                continue
            touching = [cid for cid in bubd_index.intersects(geom.boundingBox())
                        if cid != fid and geom.touches(geom_cache[cid])]
            if len(touching) == 1:
                oid = touching[0]
                if oid in already_marked:
                    continue
                o_geom, o_area = geom_cache[oid], area_cache[oid]
                if area + o_area < 39:
                    o_touching = [cid for cid in bubd_index.intersects(o_geom.boundingBox())
                                  if cid not in (fid, oid) and o_geom.touches(geom_cache[cid])]
                    bb = geom.combine(o_geom).boundingBox().buffered(50)
                    others = [cid for cid in bubd_index.intersects(bb) if cid not in (fid, oid)]
                    if others and not o_touching:
                        obiektyZbledami.extend([f, bubd_dict[oid]])
                        already_marked.update((fid, oid))
        for fid, f in bubd_dict.items():
            if fid in already_marked:
                continue
            funkcja_og, przew_funkcja = funkcja_cache[fid]
            if funkcja_og == "budynki mieszkalne" or przew_funkcja in excluded_funkcje:
                continue
            geom, area = geom_cache[fid], area_cache[fid]
            if area >= 39:
                continue
            touching = [cid for cid in bubd_index.intersects(geom.boundingBox())
                        if cid != fid and geom.touches(geom_cache[cid])]
            if not touching:
                buffer_geom = geom.buffer(15, 1)
                in_buf = [cid for cid in bubd_index.intersects(buffer_geom.boundingBox())
                          if cid != fid and geom_cache[cid].intersects(buffer_geom)]
                if any(area_cache[cid] >= 40 for cid in in_buf):
                    obiektyZbledami.append(f)
                    already_marked.add(fid)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w kontroli minimalnaBUBD: {e}")
        return []


def kontrolaAtrybutuLiczbaKondygnacji(layer):
    """
    Kontrola atrybutu 'liczbaKondygnacji' w OT_BUBD_A:
    jeśli atrybut wypełniony → musi być liczbą całkowitą >=1;
    jednorodzinne, leśniczówki, letniskowe, kempingowe 1–5, gospodarcze 1–4, [magazynowe, przemysłowe, handlowo-usługowe 1–8],
    budynki transportu i łączności 1–11, inne 1–54 dla miasta / 1-11 poza miastem.
    """
    try:
        if not layer.name().endswith("OT_BUBD_A"): 
            return []

        obiektyZbledami, marked = [], set()
        FUN_JEDNOR = {"budynek jednorodzinny", "leśniczówka", "dom letniskowy", "domek kempingowy"}
        FUN_GOSP = {"budynki produkcyjne, usługowe i gospodarcze dla rolnictwa"}
        FUN_1_8 = {"zbiorniki, silosy i budynki magazynowe", "budynki przemysłowe", "budynki handlowo-usługowe"}
        FUN_1_11 = {"budynki transportu i łączności"}        
        adms_layer = next((l for l in QgsProject.instance().mapLayers().values()
                           if l.name().endswith("OT_ADMS_A")), None)
        miasta = []
        if adms_layer:
            for f in adms_layer.getFeatures():
                if str(f["rodzaj"]).strip().lower() == "miasto":
                    miasta.append(f)
        for f in layer.getFeatures():
            lid = f["lokalnyId"]
            if lid in marked: 
                continue
            funkcja = (f["przewazajacaFunkcjaBudynku"] or "").strip().lower()
            ogolna = (f["funkcjaOgolnaBudynku"] or "").strip().lower()
            raw = (str(f["liczbaKondygnacji"] or "")).strip()
            if not raw: 
                continue
            try:
                v = int(float(raw))
                if v <= 0: 
                    raise Exception()
            except:
                obiektyZbledami.append(f)
                marked.add(lid)
                continue
            w_miescie = False
            g = f.geometry()
            for m in miasta:
                if g.intersects(m.geometry()):
                    w_miescie = True
                    break
            if funkcja in FUN_JEDNOR and not (1 <= v <= 5):
                obiektyZbledami.append(f); marked.add(lid); continue
            elif ogolna in FUN_GOSP and not (1 <= v <= 4):
                obiektyZbledami.append(f); marked.add(lid); continue
            elif ogolna in FUN_1_8 and not (1 <= v <= 8):
                obiektyZbledami.append(f); marked.add(lid); continue
            elif ogolna in FUN_1_11 and not (1 <= v <= 11):
                obiektyZbledami.append(f); marked.add(lid); continue       
            if (w_miescie and v > 54) or (not w_miescie and v > 11) or v < 1:
                obiektyZbledami.append(f); marked.add(lid)

        return obiektyZbledami

    except Exception as e:
        print("Błąd w kontrolaAtrybutuLiczbaKondygnacji:", e)
        return []
        

def kontrolaIdentyfikatoraEGiB_len(layer, plikGML):
    """
    Kontrola nadliczbowych i nadmiarowych identyfikatorów EGiB.
    Budynki EGiB: więcej niż jeden identyfikator - BŁĄD
    Budynki inne źródło niż EGiB: posiada identyfikator - BŁĄD
    """
    obiektyZbledami=[]
    if not layer or not layer.name().endswith("OT_BUBD_A"): return []
    try:
        root = plikGML.getroot() if hasattr(plikGML,"getroot") else plikGML
    except Exception as e:
        print("Błąd w odczycie GML:", e)
        return []
    ns={'gml':'http://www.opengis.net/gml/3.2',
        'ot':'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'}
    lokalnyId_to_feature = {str(f["lokalnyId"]).strip(): f for f in layer.getFeatures()}
    for m in root.findall('.//ot:OT_BUBD_A', ns):
        try:
            lid = m.findtext('ot:lokalnyId', None, ns)
            if not lid: continue
            lid = str(lid).strip()
            if lid not in lokalnyId_to_feature: continue
            feature = lokalnyId_to_feature[lid]
            raw_vals = m.findall('ot:identyfikatorEGiB', ns)
            idents=[]
            for elem in raw_vals:
                if elem.text:
                    idents.extend([v.strip() for v in elem.text.split(',') if v.strip()])
            zrodlo = (m.findtext('ot:zrodloDanychGeometrycznych','',ns) or '').strip().upper()
            if (zrodlo == 'EGIB' and len(idents) > 1) or (zrodlo != 'EGIB' and len(idents) > 0):
                obiektyZbledami.append(feature)
        except Exception as e:
            print(f"Błąd w elemencie {lid}: {e}")
            continue

    return obiektyZbledami



def kontrolaIdentyfikatoraEGiB_pattern(layer, plikGML):
    """
    Kontrola identyfikatora EGiB: dokładnie 1 wartość,
    prefiks zgodny z TERYT powiatu, dopasowanie do wzorca ^\d{6}_.+_BUD
    """
    obiektyZbledami=[]
    if not layer or not layer.name().endswith("OT_BUBD_A"): return []
    nazwa = layer.name()
    teryt_powiat = nazwa[-15:-11]
    regex = re.compile(r'^\d{6}_.+_BUD$')
    try:
        root = plikGML.getroot() if hasattr(plikGML,"getroot") else plikGML
    except Exception as e:
        print("Błąd w odczycie GML:", e)
        return []
    ns={'gml':'http://www.opengis.net/gml/3.2',
        'ot':'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'}
    lokalnyId_to_feature={str(f["lokalnyId"]).strip():f for f in layer.getFeatures()}
    for m in root.findall('.//ot:OT_BUBD_A',ns):
        try:
            lid = m.findtext('ot:lokalnyId',None,ns)
            if not lid: continue
            lid = str(lid).strip()
            if lid not in lokalnyId_to_feature: continue
            if (m.findtext('ot:zrodloDanychGeometrycznych','',ns) or '').strip().upper()!="EGIB":
                continue
            raw_vals = m.findall('ot:identyfikatorEGiB',ns)
            idents=[]
            for elem in raw_vals:
                if elem.text:
                    idents.extend([s.strip() for s in elem.text.split(',') if s.strip()])
            if len(idents)!=1:
                continue
            ident = idents[0]
            srodkowa = ident[7:-4] if len(ident)>10 else ''
            if not regex.match(ident) or len(srodkowa)>25 or not ident.startswith(teryt_powiat):
                obiektyZbledami.append(lokalnyId_to_feature[lid])
        except Exception as e:
            print(f"Błąd w elemencie {lid}: {e}")
            continue

    return obiektyZbledami
    


def granicePowiatow():
    config = configparser.ConfigParser()
    mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath())/pathlib.Path(r"python/plugins/Walidator_plikow_gml/")
    config.read(str(mainPath)+'/Walidator_plikow_gml.ini')
    granicePowiatowPath = config['DEFAULT']['granicepowiatow']
    granicePowiatow_A = QgsVectorLayer(granicePowiatowPath, 'GranicePowiatow', 'ogr')
    
    # rozbicie multipoligon na poligony
    pojedynczeGranice = processing.run("native:multiparttosingleparts", {
        'INPUT': granicePowiatow_A,
        'OUTPUT': 'memory:'
    })
    
    # zamiana typu geometrii z poligonu na linię
    granicePowiatow_L = processing.run("native:polygonstolines", {
        'INPUT': pojedynczeGranice['OUTPUT'],
        'OUTPUT': 'memory:'
    })
    return granicePowiatow_L


def czyPrzecinaGranicePowiatuDlugoscPonizej50m(layer): # kompatabilnosć w szablonie kontroli
    czyPrzecinaGranicePowiatuDlugoscPonizej25m(layer)
def czyPrzecinaGranicePowiatuDlugoscPonizej25m(layer):
    from qgis.core import QgsSpatialIndex
    obiektyZbledami = []
    try:
        if not layer or not layer.isValid() or not layer.name().endswith("OT_RTLW_L"):
            return obiektyZbledami

        granica_layer = adjaMinus2cmBufor(layer)
        granica_buf_geom = None
        for g in granica_layer.getFeatures():
            if g.geometry() and g.geometry().isGeosValid():
                granica_buf_geom = g.geometry().buffer(0.10, 5)
                break
        if not granica_buf_geom:
            return obiektyZbledami
        features = [f for f in layer.getFeatures() if f['rodzaj'] == 'poziomica' and f.geometry() and f.geometry().isGeosValid()]
        index = QgsSpatialIndex()
        feat_dict = {}
        for f in features:
            index.insertFeature(f)
            feat_dict[f.id()] = f
        kod_idx = layer.fields().indexFromName("kodKarto10k")    
        for f in features:
            geom = f.geometry()
            if geom.length() >= 25:
                continue
            kod_karto = ""
            if kod_idx >= 0:
                val = f.attribute(kod_idx)
                if val not in (None, "", " ", "null", "NULL"):
                    kod_karto = str(val).strip()
            if not kod_karto:
                continue    
            intersects_other = False
            for fid in index.intersects(geom.boundingBox()):
                if fid != f.id():
                    other_geom = feat_dict[fid].geometry()
                    if other_geom and other_geom.isGeosValid() and geom.intersects(other_geom):
                        intersects_other = True
                        break
            if intersects_other:
                continue
            if granica_buf_geom.intersects(geom):
                continue
            obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print("Błąd w funkcji czyPrzecinaGranicePowiatuDlugoscPonizej25m:", e)
        return []


def czyObiektyWewnatrzPowiatu(layer, teryt):
    obiektyZbledami = []
    config = configparser.ConfigParser()
    mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath()) / "python/plugins/Walidator_plikow_gml/"
    config.read(str(mainPath / 'Walidator_plikow_gml.ini'))
    granicePowiatowPath = config['DEFAULT']['granicepowiatow']
    granicePowiatow = processing.run("native:multiparttosingleparts", {
        'INPUT': QgsVectorLayer(granicePowiatowPath, 'GranicePowiatow', 'ogr'),
        'OUTPUT': 'memory:'
    })['OUTPUT']
    request = QgsFeatureRequest(QgsExpression(f"JPT_KOD_JE = '{teryt}'"))
    granica_powiatu = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "granica powiatu z PRG", "memory")
    granica_powiatu.dataProvider().addFeatures(granicePowiatow.getFeatures(request))
    bufor = processing.run("native:buffer", {
        'INPUT': granica_powiatu,
        'DISTANCE': 0.02,
        'SEGMENTS': 10,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    buforLinie = processing.run("native:polygonstolines", {
        'INPUT': bufor,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    # --- WALIDACJA GEOMETRII ---
    isvalid = processing.run("qgis:checkvalidity", {
        'INPUT_LAYER': layer,
        'METHOD': 2,
        'IGNORE_RING_SELF_INTERSECTION': False,
        'VALID_OUTPUT': 'memory:',
        'INVALID_OUTPUT': 'memory:',
        'ERROR_OUTPUT': 'memory:'
    })
    valid_layer = isvalid['VALID_OUTPUT']
    przecinajace = processing.run("native:extractbylocation", {
        'INPUT': valid_layer,
        'INTERSECT': buforLinie,
        'PREDICATE': [0],
        'OUTPUT': 'memory:'
    })['OUTPUT']
    pozaBuforem = processing.run("native:extractbylocation", {
        'INPUT': valid_layer,
        'INTERSECT': bufor,
        'PREDICATE': [2],
        'OUTPUT': 'memory:'
    })['OUTPUT']
    zbiorBledow = processing.run("native:mergevectorlayers", {
        'LAYERS': [przecinajace, pozaBuforem],
        'OUTPUT': 'memory:'
    })['OUTPUT']
    geomType = valid_layer.wkbType()
    geomKind = QgsWkbTypes.geometryType(geomType)
    if geomKind == QgsWkbTypes.PolygonGeometry:
        przekroczenia = processing.run("native:difference", {
            'INPUT': zbiorBledow,
            'OVERLAY': bufor,
            'OUTPUT': 'memory:'
        })['OUTPUT']
        niezgodne = processing.run("native:polygonstolines", {
            'INPUT': przekroczenia,
            'OUTPUT': 'memory:'
        })['OUTPUT']
        niezgodne = processing.run("native:multiparttosingleparts", {
            'INPUT': niezgodne,
            'OUTPUT': 'memory:'
        })['OUTPUT']
        if niezgodne.featureCount() > 0:
            if all(lyr.name() != "granica powiatu z PRG" for lyr in QgsProject.instance().mapLayers().values()):
                QgsProject.instance().addMapLayer(granica_powiatu)
            obiektyZbledami.extend(niezgodne.getFeatures())
    elif geomKind == QgsWkbTypes.LineGeometry:
        bufor_geom = next(bufor.getFeatures()).geometry()
        for feature in zbiorBledow.getFeatures():
            geom = feature.geometry()
            if geom is None or geom.isEmpty():
                continue
            roznica = geom.difference(bufor_geom)
            if roznica and not roznica.isEmpty():
                nowy_feat = QgsFeature()
                nowy_feat.setFields(layer.fields())
                nowy_feat.setGeometry(roznica)
                nowy_feat.initAttributes(len(layer.fields()))
                for i in range(len(layer.fields())):
                    nowy_feat.setAttribute(i, feature[i])
                obiektyZbledami.append(nowy_feat)
        if obiektyZbledami and all(lyr.name() != "granica powiatu z PRG" for lyr in QgsProject.instance().mapLayers().values()):
            QgsProject.instance().addMapLayer(granica_powiatu)

    return obiektyZbledami



def czyPunktyWewnatrzPowiatu(layer):
    punktowe_warstwy = {
        "OT_SKRW_P", "OT_OIPR_P", "OT_OIOR_P",
        "OT_OIKM_P", "OT_KUPG_P", "OT_KUKO_P", "OT_BUZT_P",
        "OT_BUWT_P", "OT_BUIT_P", "OT_ADMS_P"
    }
    if not any(layer.name().endswith(suf) for suf in punktowe_warstwy):
        return []
    match = re.search(r"\.(\d{4})__", layer.name())
    if not match:
        return []
    teryt = match.group(1)
    config = configparser.ConfigParser()
    settings_dir = pathlib.Path(QgsApplication.qgisSettingsDirPath())
    plugin_path = settings_dir / "python/plugins/Walidator_plikow_gml/"
    config.read(str(plugin_path / "Walidator_plikow_gml.ini"))
    granicePowiatowPath = config['DEFAULT']['granicepowiatow']
    granice = QgsVectorLayer(granicePowiatowPath, "GranicePowiatow", "ogr")
    if not granice.isValid():
        return []
    granice = processing.run("native:multiparttosingleparts", {
        'INPUT': granice,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    req = QgsFeatureRequest(QgsExpression(f"JPT_KOD_JE = '{teryt}'"))
    granica_powiatu = QgsVectorLayer("Polygon?crs=epsg:2180", "granica", "memory")
    granica_powiatu.dataProvider().addFeatures(granice.getFeatures(req))
    if granica_powiatu.featureCount() == 0:
        return []
    bufor = processing.run("native:buffer", {
        'INPUT': granica_powiatu,
        'DISTANCE': 0.005,
        'SEGMENTS': 5,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    geometria_bufora = QgsGeometry.unaryUnion([f.geometry() for f in bufor.getFeatures()])
    if geometria_bufora is None or geometria_bufora.isEmpty():
        return []
        
    obiektyZbledami = []
    for feat in layer.getFeatures():
        geom = feat.geometry()
        if geom and not geometria_bufora.contains(geom):
            obiektyZbledami.append(feat)
            
    return obiektyZbledami


def czyPunktyWewnatrzPowiatu_RTPW(layer):
    if not layer.isValid():
        return []
        
    layer_name = layer.name()
    if not layer_name.endswith("OT_RTPW_P"):
        return []
    match = re.search(r"\.(\d{4})__", layer_name)
    if not match:
        return []
    teryt = match.group(1)
    config = configparser.ConfigParser()
    settings_dir = pathlib.Path(QgsApplication.qgisSettingsDirPath())
    plugin_path = settings_dir / "python/plugins/Walidator_plikow_gml/"
    config.read(str(plugin_path / "Walidator_plikow_gml.ini"))
    granicePowiatowPath = config['DEFAULT']['granicepowiatow']
    granice = QgsVectorLayer(granicePowiatowPath, "GranicePowiatow", "ogr")
    if not granice.isValid():
        return []
    granice_single = processing.run("native:multiparttosingleparts", {
        'INPUT': granice,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    req = QgsFeatureRequest(QgsExpression(f"JPT_KOD_JE = '{teryt}'"))
    granica_powiatu = QgsVectorLayer("Polygon?crs=epsg:2180", "granica", "memory")
    granica_powiatu.dataProvider().addFeatures(granice_single.getFeatures(req))
    if granica_powiatu.featureCount() == 0:
        return []
    bufor = processing.run("native:buffer", {
        'INPUT': granica_powiatu,
        'DISTANCE': 0.005,
        'SEGMENTS': 5,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    geometria_bufora = QgsGeometry.unaryUnion([f.geometry() for f in bufor.getFeatures()])
    if geometria_bufora is None or geometria_bufora.isEmpty():
        return []
        
    obiektyZbledami = []
    for feat in layer.getFeatures():
        geom = feat.geometry()
        if geom and not geometria_bufora.contains(geom):
            obiektyZbledami.append(feat)
            
    return obiektyZbledami


def czyOdleglosciMiedzyPoziomicami2m(layer):
    obiekty_z_bledami = []
    
    poziomice = processing.run("native:extractbyexpression", {
        'INPUT': layer,
        'EXPRESSION': '"rodzaj" = \'poziomica\' and "kodKarto10k" != \'\'',
        'FAIL_OUTPUT': 'memory:',
        'OUTPUT': 'memory:'
    })    
    # bufor
    bufor = processing.run("native:buffer", {
        'INPUT': poziomice['OUTPUT'],
        'DISTANCE': 0.999,
        'END_CAP_STYLE': 0,
        'JOIN_STYLE': 0,
        'MITER_LIMIT': 2,
        'SEGMENTS': 5,
        'DISSOLVE': False,
        'OUTPUT': 'memory:'
    })  
    union = processing.run("native:union", {
        'INPUT': bufor['OUTPUT'],
        'OUTPUT': 'memory:'
    })   
    nakladanie = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "nakładania buforów poziomic", "memory")
    geom_union = []
    for f in union['OUTPUT'].getFeatures():
        g = f.geometry()
        geom_wkt = g.asWkt()
        obiektBledny = False
        if not geom_wkt in geom_union:
            geom_union.append(geom_wkt)
        else:
            wysokosci = []
            x = bufor['OUTPUT'].getFeatures(g.boundingBox())
            for obiekt_bliski in x:
                if g.intersects(obiekt_bliski.geometry()) == True:
                    if obiekt_bliski['wysokosc'] in wysokosci:
                        obiektBledny = True
                    else:
                        wysokosci.append(obiekt_bliski['wysokosc'])
            if obiektBledny:
                continue
            nakladanie.dataProvider().addFeatures([f])
    
    QgsProject.instance().addMapLayer(nakladanie)   
    extractbylocation1 = processing.run("native:extractbylocation", {
        'INPUT': bufor['OUTPUT'],
        'PREDICATE': 0,
        'INTERSECT': nakladanie,
        'OUTPUT': 'memory:'
    })    
    extractbylocation2 = processing.run("native:extractbylocation", {
        'INPUT': poziomice['OUTPUT'],
        'PREDICATE': 6,
        'INTERSECT':extractbylocation1['OUTPUT'],
        'OUTPUT': 'memory:'
    })   
    if extractbylocation2['OUTPUT'].featureCount() > 0:
        for obj in extractbylocation2['OUTPUT'].getFeatures():
            obiekty_z_bledami.append(obj)
    
    return obiekty_z_bledami


def nadmiernaSegmentacja(layer):
    try:
        id_to_feature = {f.id(): f for f in layer.getFeatures()}
        obiekty_z_bledami = []
        spatial_index = QgsSpatialIndex(layer.getFeatures(), flags=QgsSpatialIndex.FlagStoreFeatureGeometries)
        for obj1 in layer.getFeatures():
            geom1 = obj1.geometry()
            if not geom1 or geom1.isEmpty():
                continue
            nearest = spatial_index.nearestNeighbor(geom1, 2, 0)
            for nn in nearest:
                if obj1.id() == nn:
                    continue
                obj2 = id_to_feature.get(nn)
                geom2 = obj2.geometry() if obj2 else None
                if not geom2 or geom2.isEmpty():
                    continue
                if geom1.distance(geom2) < 0.01 and obj1.attributes()[2:] == obj2.attributes()[2:] and not geom1.equals(geom2):
                    intersection = geom1.intersection(geom2)
                    if (intersection and (intersection.length() > 0.02 or intersection.area() > 0.0)):
                        obiekty_z_bledami.append(obj1)
                        break
        return obiekty_z_bledami
    except Exception as e:
        print(f"Błąd w funkcji nadmiernaSegmentacja dla warstwy {layer.name()}: {e}")
        return []


def nadmiernaSegmentacja_rtwl(layer):
    poziomice = processing.run("native:extractbyexpression", {
        'INPUT': layer,
        'EXPRESSION': '"rodzaj" = \'poziomica\' and "kodKarto10k" != \'\'',
        'FAIL_OUTPUT': 'memory:',
        'OUTPUT': 'memory:'
    })    
    id_to_feature = {feature.id(): feature for feature in poziomice['OUTPUT'].getFeatures()}
    obiekty_z_bledami = []
    hash_to_ids = {}
    spatial_index = QgsSpatialIndex(poziomice['OUTPUT'].getFeatures(), flags=QgsSpatialIndex.FlagStoreFeatureGeometries)
    
    for feature in id_to_feature.values():
        spatial_index.addFeature(feature)
    for feature_id, feature in id_to_feature.items():
        # Utwórz hash na podstawie atrybutów (pomijając pierwsze dwa)
        attrs_hash = hash(tuple(feature.attributes()[2:]))
        if attrs_hash in hash_to_ids:
            hash_to_ids[attrs_hash].append(feature_id)
        else:
            hash_to_ids[attrs_hash] = [feature_id]
    for attrs_hash, ids in hash_to_ids.items():
       if len(ids) > 1: # Jeśli więcej niż jeden obiekt ma ten sam hash
           for id in ids:
               obj1 = id_to_feature[id]
               bbox = obj1.geometry().boundingBox()
               bbox.grow(0.01)
               candidates_ids = spatial_index.intersects(bbox)
               candidates_ids.remove(id)
               for candidate_id in candidates_ids:
                   if candidate_id in ids:
                        obj2 = id_to_feature[candidate_id]
                        if not obj1 in obiekty_z_bledami and obj1.geometry().distance(obj2.geometry()) < 0.01 and not obj1.geometry().equals(obj2.geometry()):
                            obiekty_z_bledami.append(obj1)
                            break
    
    return obiekty_z_bledami


def przewerteksowanie(layer):
    obiekty_z_bledami = []
    
    poziomice = processing.run("native:extractbyexpression", {
        'INPUT': layer,
        'EXPRESSION': '"rodzaj" = \'poziomica\'',
        'FAIL_OUTPUT': 'memory:',
        'OUTPUT': 'memory:'
    })
    
    simplifygeometries = processing.run("native:simplifygeometries", {
        'INPUT': poziomice['OUTPUT'],
        'METHOD': 0, # DP
        'TOLERANCE': 0.05, # 5 cm strzałki
        'OUTPUT': 'memory:'
        })
    
    roznica = processing.run("native:difference", {
        'INPUT': poziomice['OUTPUT'],
        'OVERLAY': simplifygeometries['OUTPUT'],
        'OUTPUT': 'memory:'
    })
    
    pojedynczeObiekty = processing.run("native:multiparttosingleparts", {
        'INPUT': roznica['OUTPUT'],
        'OUTPUT': 'memory:'
    })
    
    if pojedynczeObiekty['OUTPUT'].featureCount() > 0:
        for obj in pojedynczeObiekty['OUTPUT'].getFeatures():
            obiekty_z_bledami.append(obj)
    
    return obiekty_z_bledami


def przecieciaLiniiNapieciaPodziemna(layer):
    try:
        obiekty_z_bledami, sprawdzone_gml = [], set()
        if not layer.name().endswith("SULN_L"):
            return []
        suln_layers = QgsProject().instance().mapLayersByName(layer.name())
        buwt_layers = QgsProject().instance().mapLayersByName(layer.name().replace("OT_SULN_L", "OT_BUWT_P"))
        if not suln_layers:
            return []
        OT_SULN_L_layer = suln_layers[0]
        OT_BUWT_P_layer = buwt_layers[0] if buwt_layers else None
        index = QgsSpatialIndex()
        if OT_BUWT_P_layer:
            for feat in OT_BUWT_P_layer.getFeatures():
                index.insertFeature(feat)
        granica_bufor = adjaMinus2cmBufor(layer)
        for feature in OT_SULN_L_layer.getFeatures():
            geom = feature.geometry()
            if not geom or not geom.isGeosValid():
                continue
            touches_boundary = False
            if granica_bufor:
                touches_boundary = any(geom.intersects(boundary.geometry()) for boundary in granica_bufor.getFeatures())
            if touches_boundary and geom.length() <= 320:
                continue  # pomijamy krótkie linie styczne do granicy
            rodzaj = str(feature['rodzaj'] or '').strip()
            kod = str(feature['kodKarto10k'] or '').strip()
            gml_id = str(feature['gml_id'] or feature.id()).strip()
            if rodzaj == 'linia elektroenergetyczna średniego napięcia':
                if kod != '0010_444' and gml_id not in sprawdzone_gml:
                    obiekty_z_bledami.append(feature)
                    sprawdzone_gml.add(gml_id)
                continue
            if rodzaj not in ['linia elektroenergetyczna wysokiego napięcia', 'linia elektroenergetyczna najwyższego napięcia']:
                continue
            punkty = list(geom.vertices())
            if len(punkty) <= 3:
                styk = []
                if OT_BUWT_P_layer:
                    for sid in index.intersects(geom.boundingBox()):
                        slup = OT_BUWT_P_layer.getFeature(sid)
                        if str(slup['rodzaj']).strip() == 'słup energetyczny' and geom.intersects(slup.geometry()):
                            styk.append(slup.id())
                n = len(styk)
                if gml_id not in sprawdzone_gml:
                    if (n == 0 and kod != '0010_444') or (n == 1 and kod == '') or ((n in [2, 3]) and kod in ['', '0010_444']):
                        obiekty_z_bledami.append(feature)
                        sprawdzone_gml.add(gml_id)
                continue
            if len(punkty) >= 4:
                pkt = [QgsPointXY(p.x(), p.y()) for p in punkty[1:-1]]
            else:
                pkt = [QgsPointXY(p.x(), p.y()) for p in punkty]
            if len(pkt) < 2:
                continue
            linia = QgsGeometry.fromPolylineXY(pkt)
            przecina = False
            if OT_BUWT_P_layer:
                for sid in index.intersects(linia.boundingBox()):
                    slup = OT_BUWT_P_layer.getFeature(sid)
                    if str(slup['rodzaj']).strip() == 'słup energetyczny' and linia.intersects(slup.geometry()):
                        przecina = True
                        break
            if gml_id not in sprawdzone_gml:
                if (not przecina and kod == '') or (przecina and kod == '0010_444'):
                    obiekty_z_bledami.append(feature)
                    sprawdzone_gml.add(gml_id)

        return obiekty_z_bledami

    except Exception as e:
        print(f"Błąd w przecieciaLiniiNapieciaPodziemna: {e}")
        return []


def przecieciaLiniiNapieciaNadziemna(layer):
    try:
        obiekty_z_bledami, sprawdzone_gml = [], set()
        if not layer.name().endswith("SULN_L"):
            return []
        suln = QgsProject().instance().mapLayersByName(layer.name())
        buwt = QgsProject().instance().mapLayersByName(layer.name().replace("OT_SULN_L", "OT_BUWT_P"))
        if not suln:
            return []
        linie = suln[0]
        slupy = buwt[0] if buwt else None
        index = QgsSpatialIndex()
        if slupy:
            for f in slupy.getFeatures():
                index.insertFeature(f)
        granica_bufor = adjaMinus2cmBufor(layer)
        for f in linie.getFeatures():
            geom = f.geometry()
            if geom is None or not geom.isGeosValid():
                continue
            touches_boundary = False
            if granica_bufor:
                touches_boundary = any(geom.intersects(boundary.geometry()) for boundary in granica_bufor.getFeatures())
            if touches_boundary and geom.length() <= 320:
                continue
            rodzaj = str(f['rodzaj'] or '').strip()
            kod = str(f['kodKarto10k'] or '').strip()
            if kod == '0010_444':
                continue
            if rodzaj not in ['linia elektroenergetyczna wysokiego napięcia', 'linia elektroenergetyczna najwyższego napięcia']:
                continue
            punkty = [v for v in geom.vertices()]
            if len(punkty) <= 3:
                styk = []
                if slupy:
                    for sid in index.intersects(geom.boundingBox()):
                        s = slupy.getFeature(sid)
                        if str(s['rodzaj']).strip() == 'słup energetyczny' and geom.intersects(s.geometry()):
                            styk.append(s.id())
                n = len(styk)
                gml_id = f['gml_id'] if 'gml_id' in f.fields().names() else f.id()
                if gml_id not in sprawdzone_gml:
                    if (n == 0 and kod == '0010_446') or (n == 1 and kod == '') or ((n in [2, 3]) and kod != '0010_446'):
                        obiekty_z_bledami.append(f)
                        sprawdzone_gml.add(gml_id)
                continue
            if len(punkty) >= 4:
                pkt = [QgsPointXY(p.x(), p.y()) for p in punkty[1:-1]]
            else:
                pkt = [QgsPointXY(p.x(), p.y()) for p in punkty]
            if len(pkt) < 2:
                continue
            linia = QgsGeometry.fromPolylineXY(pkt)
            przecina = False
            if slupy:
                for sid in index.intersects(linia.boundingBox()):
                    s = slupy.getFeature(sid)
                    if str(s['rodzaj']).strip() == 'słup energetyczny' and linia.intersects(s.geometry()):
                        przecina = True
                        break
            gml_id = f['gml_id'] if 'gml_id' in f.fields().names() else f.id()
            if gml_id not in sprawdzone_gml:
                if (przecina and kod != '0010_446') or (not przecina and kod == '0010_446'):
                    obiekty_z_bledami.append(f)
                    sprawdzone_gml.add(gml_id)

        return obiekty_z_bledami

    except Exception as e:
        print(f"Błąd w przecieciaLiniiNapieciaNadziemna: {e}")
        return []


def kontrola_OT_ADMS_P_z_OT_ADMS_A(layer):
    obiekty_z_bledami = []
    OT_ADMS_A_layer = None
    OT_ADMS_P_layer = None
    if layer.name()[-6:] == 'ADMS_P':
        try:
            OT_ADMS_A_layer = QgsProject().instance().mapLayersByName(layer.name().replace("OT_ADMS_P","OT_ADMS_A"))[0]
            OT_ADMS_P_layer = QgsProject().instance().mapLayersByName(layer.name())[0]
        except:
            return obiekty_z_bledami
    if OT_ADMS_A_layer and OT_ADMS_P_layer:
        identyfikatory_bledow = set() # Zbiór do śledzenia identyfikatorów błędnych obiektów
        nazwy_adms_p = set([feature['nazwa'] for feature in OT_ADMS_P_layer.getFeatures()])
        nazwy_adms_a = set([feature['nazwa'] for feature in OT_ADMS_A_layer.getFeatures()])
        # Sprawdzenie przecięcia i nazw dla obiektów z OT_ADMS_A
        for OT_ADMS_A_feature in OT_ADMS_A_layer.getFeatures():
            is_intersected = False
            nazwa_adms_a = OT_ADMS_A_feature['nazwa']
            # Sprawdzenie przecięcia dla każdego obiektu z OT_ADMS_P
            for OT_ADMS_P_feature in OT_ADMS_P_layer.getFeatures():
                nazwa_adms_p = OT_ADMS_P_feature['nazwa']
                if OT_ADMS_P_feature.geometry().intersects(OT_ADMS_A_feature.geometry()) or OT_ADMS_A_feature.geometry().contains(OT_ADMS_P_feature.geometry()):
                    is_intersected = True
                    break
            if not is_intersected or nazwa_adms_a not in nazwy_adms_p: #... oraz czy nazwy z OT_ADMS_A występują w OT_ADMS_P
                if OT_ADMS_A_feature.id() not in identyfikatory_bledow:
                    obiekty_z_bledami.append(OT_ADMS_A_feature)
                    identyfikatory_bledow.add(OT_ADMS_A_feature.id())
            # Sprawdzenie, czy nazwy z OT_ADMS_P występują w OT_ADMS_A
            if OT_ADMS_P_feature['nazwa'] not in nazwy_adms_a and OT_ADMS_P_feature.id() not in identyfikatory_bledow:
                obiekty_z_bledami.append(OT_ADMS_P_feature)
                identyfikatory_bledow.add(OT_ADMS_P_feature.id())
    
    return obiekty_z_bledami


def kontrolaTERCpunkt(layer):
    obiektyZbledami = []
    adms = None
    adja = None
    if layer.name()[-6:] == 'ADMS_P':
        try:
            adja = QgsProject().instance().mapLayersByName(layer.name().replace("OT_ADMS_P","OT_ADJA_A"))[0]
            adms = QgsProject().instance().mapLayersByName(layer.name())[0]
        except:
            return obiektyZbledami 
    # Zestaw do śledzenia już dodanych identyfikatorów TERYT
    przetworzone_teryt = set()
    
    # Sprawdzenie, czy są warstwy
    if adms and adja:
        expression = """ "rodzaj" = 'gmina' """ 
        # Wykonaj selekcję
        adja.selectByExpression(expression, QgsVectorLayer.SetSelection)
        selected = adja.selectedFeatures()
        # Przygotuj listę identyfikatorów TERYT z warstwy ADJA jako tekst
        identyfikatory_teryt = {str(feature["identyfikatorTerytJednostki"]).zfill(7): feature for feature in selected}
        # Przygotuj słownik dla ADMS_P: klucz to IdentyfikatorTERC, wartość to lista nazw
        identyfikatory_terc_adms = {}
        for feature in adms.getFeatures():
           teryt_adms = str(feature["IdentyfikatorTERC"]).zfill(7)
           if teryt_adms in identyfikatory_terc_adms:
               identyfikatory_terc_adms[teryt_adms].append(feature)
           else:
               identyfikatory_terc_adms[teryt_adms] = [feature]
        # Dla każdego identyfikatora TERYT/TERC
        for teryt_adms, adms_features in identyfikatory_terc_adms.items():
           if teryt_adms not in identyfikatory_teryt:
               for adms_feature in adms_features:
                   obiektyZbledami.append(adms_feature)
        adja.removeSelection()
    
    return obiektyZbledami


def kontrolaTERCpowierzchnia(layer):
    obiektyZbledami = []
    adms = None
    adja = None
    if layer.name()[-6:] == 'ADMS_A':
        try:
            adja = QgsProject().instance().mapLayersByName(layer.name().replace("ADMS","ADJA"))[0]
            adms = QgsProject().instance().mapLayersByName(layer.name())[0]
        except:
            return obiektyZbledami
    # Zestaw do śledzenia już dodanych identyfikatorów TERYT
    przetworzone_teryt = set()
    
    # Sprawdzenie, czy jsą warstwy
    if adms and adja:
        # Iteruj przez warstwy
        expression = """ "rodzaj" = 'gmina' """ 
        # Wykonaj selekcję na ADJA_A
        adja.selectByExpression(expression, QgsVectorLayer.SetSelection)
        selected = adja.selectedFeatures()
        # Przygotuj listę identyfikatorów TERYT z warstwy ADJA jako tekst
        identyfikatory_teryt = {str(feature["identyfikatorTerytJednostki"]).zfill(7): feature  for feature in selected}
        # Przygotuj słownik dla ADMS_A: klucz to IdentyfikatorTERC, wartość to lista nazw
        identyfikatory_terc_adms = {}
        for feature in adms.getFeatures():
            teryt_adms = str(feature["IdentyfikatorTERC"]).zfill(7)
            if teryt_adms in identyfikatory_terc_adms:
                identyfikatory_terc_adms[teryt_adms].append(feature)
            else:
                identyfikatory_terc_adms[teryt_adms] = [feature]
        #Sprawdzenie: kod TERYT w ADMS_A, ale nie w ADJA
        for teryt_adms, adms_features in identyfikatory_terc_adms.items():
           if teryt_adms not in identyfikatory_teryt:
               for adms_feature in adms_features:
                    obiektyZbledami.append(adms_feature)
        adja.removeSelection()
    
    return obiektyZbledami


def fullCoverage(layer):
    obiektyZbledami = []
    pokrycie = []
    wszystkieObiektyZPokrycia = QgsVectorLayer("Polygon?crs=epsg:2180", "Wszystkie obiekty z pokrycia", "memory")
    config = configparser.ConfigParser()
    mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath()) / "python/plugins/Walidator_plikow_gml"
    config.read(str(mainPath / "Walidator_plikow_gml.ini"))
    granicePowiatowPath = config['DEFAULT']['granicepowiatow']
    granicePowiatow = QgsVectorLayer(granicePowiatowPath, 'GranicePowiatow', 'ogr')

    granicePowiatow = processing.run("native:multiparttosingleparts", {
        'INPUT': granicePowiatow,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    match = re.search(r"\.(\d{4})__", layer.name())
    if not match:
        return []
    terytPowiatu = match.group(1)
    powiat_z_PRG = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "granica powiatu z PRG", "memory")
    adja_geom = None
    for obj in granicePowiatow.getFeatures():
        if str(obj['JPT_KOD_JE']) == str(terytPowiatu):
            powiat_z_PRG.dataProvider().addFeatures([obj])
            adja_geom = obj.geometry()
            break
    for lyr in QgsProject.instance().mapLayers().values():
        if "OT_PT" in lyr.name():
            pokrycie.append(lyr)
    for l in pokrycie:
        for feature in l.getFeatures():
            wszystkieObiektyZPokrycia.dataProvider().addFeatures([feature])

    # WALIDACJA GEOMETRII
    isvalid = processing.run("qgis:checkvalidity", {
        'INPUT_LAYER': wszystkieObiektyZPokrycia,
        'METHOD': 2,
        'IGNORE_RING_SELF_INTERSECTION': False,
        'VALID_OUTPUT': 'memory:',
        'INVALID_OUTPUT': 'memory:',
        'ERROR_OUTPUT': 'memory:'
    })
    if isvalid['INVALID_OUTPUT'].featureCount() > 0:
        isvalid['INVALID_OUTPUT'].setName("błędne geometrie obiektów pokrycia terenu")
        QgsProject.instance().addMapLayer(isvalid['INVALID_OUTPUT'])
    if isvalid['ERROR_OUTPUT'].featureCount() > 0:
        isvalid['ERROR_OUTPUT'].setName("lokalizacje błędów geometrii obiektów pokrycia terenu")
        QgsProject.instance().addMapLayer(isvalid['ERROR_OUTPUT'])
    valid = isvalid['VALID_OUTPUT']

    # NAKŁADANIA
    nakladanie = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", 
                                "nakładania w pokryciu terenu", "memory")
    pr = nakladanie.dataProvider()
    index_layer = QgsSpatialIndex(layer.getFeatures())
    geom_union = set()
    union = processing.run("native:union", {
        'INPUT': valid,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    nakladanie.startEditing()
    for f in union.getFeatures():
        g = f.geometry()
        if not g or g.isEmpty():
            continue
        g.normalize()
        wkb = g.asWkb()
        if isinstance(wkb, QByteArray):
            wkb = bytes(wkb)
        geom_hash = wkb.hex()
        if geom_hash in geom_union:
            if g.area() > 0.009:
                pr.addFeatures([f])
                centroid = g.centroid().asPoint()
                nearest_ids = index_layer.nearestNeighbor(centroid, 1)
                if nearest_ids:
                    nearest = layer.getFeature(nearest_ids[0])
                    if all(nearest.id() != o.id() for o in obiektyZbledami):
                        obiektyZbledami.append(nearest)
        else:
            geom_union.add(geom_hash)
    nakladanie.commitChanges()
    nakladanie.startEditing()
    for n in nakladanie.getFeatures():
        n.setAttribute(0, 'nie dotyczy')
        nakladanie.updateFeature(n)
    nakladanie.commitChanges()
    if nakladanie.featureCount() > 0:
        nakladanie.setName("nakładania w pokryciu terenu")
        QgsProject.instance().addMapLayer(nakladanie)

    # DZIURY
    dziury = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "dziury w pokryciu terenu", "memory")
    buforMinus1cm = processing.run("native:buffer", {
        'INPUT': powiat_z_PRG,
        'DISTANCE': -0.02,
        'SEGMENTS': 10,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    roznica = processing.run("native:difference", {
        'INPUT': buforMinus1cm,
        'OVERLAY': valid,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    pojedynczeDziury = processing.run("native:multiparttosingleparts", {
        'INPUT': roznica,
        'OUTPUT': 'memory:'
    })['OUTPUT']

    dziury.startEditing()
    spatial_index_layer = QgsSpatialIndex(layer.getFeatures())
    for n in pojedynczeDziury.getFeatures():
        if n.geometry().area() > 0.009:

            nowy = QgsFeature(dziury.fields())
            nowy.setGeometry(n.geometry())
            nowy.setAttribute(0, 'nie dotyczy')
            dziury.addFeature(nowy)
            obiektyZbledami.append(nowy)

            rect = n.geometry().boundingBox()
            ids = spatial_index_layer.intersects(rect)
            for fid in ids:
                f = layer.getFeature(fid)
                if f.geometry().intersects(n.geometry()):
                    if f.id() not in [o.id() for o in obiektyZbledami]:
                        obiektyZbledami.append(f)
                    break
    dziury.commitChanges()
    if dziury.featureCount() > 0:
        QgsProject.instance().addMapLayer(dziury)

    # NIESPÓJNOŚCI WERTEKSÓW
    niespojnosci = QgsVectorLayer("Point?crs=epsg:2180&field=gml_id:string(254)", "niespójność werteksów", "memory")
    pr = niespojnosci.dataProvider()
    geometrie_dict = {}
    licznik_wierzcholkow = defaultdict(set)
    index = QgsSpatialIndex()
    for f in valid.getFeatures():
        fid = f.id()
        geometrie_dict[fid] = f
        index.insertFeature(f)
        geom = f.geometry()
        for v in geom.vertices():
            xy = (round(v.x(), 2), round(v.y(), 2))
            licznik_wierzcholkow[xy].add(fid)

    tolerancja = 0.01
    for xy, ids in licznik_wierzcholkow.items():
        if len(ids) != 1:
            continue
        fid = list(ids)[0]
        geom1 = geometrie_dict[fid].geometry()
        pt = QgsPointXY(*xy)
        geom_pt = QgsGeometry.fromPointXY(pt)
        candidates = index.intersects(geom1.boundingBox())
        for cid in candidates:
            if cid == fid:
                continue
            geom2 = geometrie_dict[cid].geometry()
            if geom2.touches(geom_pt):
                if any(QgsPointXY(round(p.x(), 2), round(p.y(), 2)).distance(pt) < tolerancja for p in geom2.vertices()):
                    continue
                feat = QgsFeature(niespojnosci.fields())
                feat.setGeometry(geom_pt)
                feat.setAttribute(0, 'nie dotyczy')
                pr.addFeature(feat)
                for f_layer in layer.getFeatures(QgsFeatureRequest().setFilterRect(geom_pt.boundingBox())):
                    if f_layer.geometry().intersects(geom_pt):
                        if f_layer.id() not in [o.id() for o in obiektyZbledami]:
                            obiektyZbledami.append(f_layer)
                        break
    if niespojnosci.featureCount() > 0:
        QgsProject.instance().addMapLayer(niespojnosci)
    for niesp in niespojnosci.getFeatures():
        if niesp.id() not in [o.id() for o in obiektyZbledami]:
            obiektyZbledami.append(niesp)

    return obiektyZbledami


def jednostkaEwidencyjnaFullCoverage(layer):
    obiektyZbledami = []
    wszystkieJednostkiEwidencyjne = QgsVectorLayer("Polygon?crs=epsg:" + str(4326), "Wszystkie jednostki ewidencyjne", "memory")
    warstwy = []
    for layer_id, lyr in QgsProject.instance().mapLayers().items():
        if lyr.name().__contains__("EGB_JednostkaEwidencyjna"):
            reprojectlayer = processing.run("native:reprojectlayer", {
                'INPUT': lyr,
                'TARGET_CRS':QgsCoordinateReferenceSystem('EPSG:4326'),
                'OUTPUT': 'memory:'
            })
            warstwy.append(reprojectlayer['OUTPUT'])   
    for l in warstwy:
        for feature in l.getFeatures():
            wszystkieJednostkiEwidencyjne.dataProvider().addFeatures([feature])    
    if not QgsProject.instance().mapLayersByName("nakładania w pokryciu jednostek ewidencyjnych") and not QgsProject.instance().mapLayersByName("dziury w pokryciu jednostek ewidencyjnych"):
        nakladanie = QgsVectorLayer("Polygon?crs=epsg:4326&field=gml_id:string(254)", "nakładania w pokryciu jednostek ewidencyjnych", "memory")
        dziury = QgsVectorLayer("Polygon?crs=epsg:4326&field=gml_id:string(254)", "dziury pomiędzy jednostkami ewidencyjnymi", "memory")
        
        isvalid_wszystkieObiektyZPokrycia = processing.run("qgis:checkvalidity", {
            'INPUT_LAYER': wszystkieJednostkiEwidencyjne,
            'METHOD': 2,
            'IGNORE_RING_SELF_INTERSECTION': False,
            'VALID_OUTPUT': 'memory:',
            'INVALID_OUTPUT': 'memory:',
            'ERROR_OUTPUT': 'memory:'
        })       
        if isvalid_wszystkieObiektyZPokrycia['INVALID_OUTPUT'].featureCount() > 0:
            isvalid_wszystkieObiektyZPokrycia['INVALID_OUTPUT'].setName("błędne geometrie obiektów jednostek ewidencyjnych")
            QgsProject.instance().addMapLayer(isvalid_wszystkieObiektyZPokrycia['INVALID_OUTPUT'])       
        if isvalid_wszystkieObiektyZPokrycia['ERROR_OUTPUT'].featureCount() > 0:
            isvalid_wszystkieObiektyZPokrycia['ERROR_OUTPUT'].setName("lokalizacje błędów geometrii obiektów jednostek ewidencyjnych")
            QgsProject.instance().addMapLayer(isvalid_wszystkieObiektyZPokrycia['ERROR_OUTPUT'])       
        union = processing.run("native:union", {
            'INPUT': isvalid_wszystkieObiektyZPokrycia['VALID_OUTPUT'],
            'OUTPUT': 'memory:'
        })       
        pojedynczeNakladania = processing.run("native:multiparttosingleparts", {
            'INPUT': union['OUTPUT'],
            'OUTPUT': 'memory:'
        })        
        geom_union = []
        for f in pojedynczeNakladania['OUTPUT'].getFeatures():
            g = f.geometry()
            geom_wkt = g.asWkt()
            if not geom_wkt in geom_union:
                geom_union.append(geom_wkt)
            else:
                nakladanie.dataProvider().addFeatures([f])
        
        if nakladanie.featureCount() > 0:
            nakladanie.startEditing()
            for n in nakladanie.getFeatures():
                n.setAttribute(0,'nie dotyczy')
                nakladanie.updateFeature(n)
                obiektyZbledami.append(n)
            nakladanie.commitChanges()
            QgsProject.instance().addMapLayer(nakladanie)
        
        dissolve = processing.run("native:dissolve", {
            'INPUT': isvalid_wszystkieObiektyZPokrycia['VALID_OUTPUT'],
            'FIELD': [],
            'OUTPUT': 'memory:'
        })        
        deleteholes = processing.run("native:deleteholes", {
            'INPUT': dissolve['OUTPUT'],
            'OUTPUT': 'memory:'
        })        
        roznica = processing.run("native:difference", {
            'INPUT': deleteholes['OUTPUT'],
            'OVERLAY': dissolve['OUTPUT'],
            'OUTPUT': 'memory:'
        })        
        multiparttosingleparts = processing.run("native:multiparttosingleparts", {
            'INPUT': roznica['OUTPUT'],
            'OUTPUT': 'memory:'
        })        
        if not QgsProject.instance().mapLayersByName("dziury pomiędzy jednostkami ewidencyjnymi"):
            dziury.startEditing()
            for n in multiparttosingleparts['OUTPUT'].getFeatures():
                if n.geometry().area() > 0: # 0- bez tolerancji
                    nowyRekord = QgsFeature(dziury.fields())
                    nowyRekord.setAttribute(0,'nie dotyczy')
                    nowyRekord.setGeometry(n.geometry())
                    dziury.addFeature(nowyRekord)
                    obiektyZbledami.append(nowyRekord)
            dziury.commitChanges()
            
            if dziury.featureCount() > 0:
                QgsProject.instance().addMapLayer(dziury)
    
    return obiektyZbledami


def boundaryPTWP(layer):
    try:
        obiektyZbledami = []
        if not layer or not layer.isValid() or not layer.name().endswith("OT_RTLW_L"):
            return []
        ptwp_layer = next((l for l in QgsProject.instance().mapLayers().values()
                           if l.name().endswith("OT_PTWP_A")), None)
        if not ptwp_layer or not ptwp_layer.isValid():
            return []

        ptwp_index = QgsSpatialIndex()
        ptwp_geometrie = {}
        for feat in ptwp_layer.getFeatures():
            geom = feat.geometry()
            if geom and not geom.isEmpty():
                buf_geom = geom.buffer(-0.02, 8)
                if buf_geom and not buf_geom.isEmpty():
                    ptwp_index.insertFeature(feat)
                    ptwp_geometrie[feat.id()] = buf_geom

        for feat in layer.getFeatures():
            rodzaj = str(feat["rodzaj"]).strip().lower() if "rodzaj" in feat.fields().names() else ""
            if rodzaj not in ("skarpa", "wąwóz"):
                continue
            geom = feat.geometry()
            if not geom or geom.isEmpty():
                continue
            candidate_ids = ptwp_index.intersects(geom.boundingBox())
            for pid in candidate_ids:
                buf_geom = ptwp_geometrie.get(pid)
                if not buf_geom:
                    continue
                intersect = geom.intersection(buf_geom)
                if intersect and not intersect.isEmpty():
                    nowy_feat = QgsFeature()
                    nowy_feat.setFields(layer.fields())
                    nowy_feat.setGeometry(intersect)
                    nowy_feat.initAttributes(len(layer.fields()))
                    for i in range(len(layer.fields())):
                        nowy_feat.setAttribute(i, feat[i])
                    gml_idx = layer.fields().indexFromName("gml_id")
                    if gml_idx >= 0:
                        nowy_feat.setAttribute(gml_idx, str(feat["gml_id"]))
                    obiektyZbledami.append(nowy_feat)
                    break
        return obiektyZbledami
    except Exception as e:
        print("Błąd w funkcji boundaryPTWP_skarpa_wawoz:", e)
        return []

def przecinanieRTLW_BUZT(layer):
    try:
        obiektyZbledami = []
        if not layer or not layer.isValid() or not layer.name().endswith("OT_RTLW_L"):
            return []
        buzt_layer = next((l for l in QgsProject.instance().mapLayers().values()
                           if l.name().endswith("OT_BUZT_A")), None)
        if not buzt_layer or not buzt_layer.isValid():
            return []
        buzt_index = QgsSpatialIndex()
        buzt_geometrie = {}
        for feat in buzt_layer.getFeatures():
            geom = feat.geometry()
            if geom and not geom.isEmpty():
                buf_geom = geom.buffer(-0.02, 8)
                if buf_geom and not buf_geom.isEmpty():
                    buzt_index.insertFeature(feat)
                    buzt_geometrie[feat.id()] = buf_geom
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty():
                continue
            kod_idx = layer.fields().indexFromName("kodKarto10k")
            kod_karto = ""
            if kod_idx >= 0:
                val = feat.attribute(kod_idx)
                if val not in (None, "", " ", "null", "NULL"):
                    kod_karto = str(val).strip()
            if not kod_karto:
                continue           
            candidate_ids = buzt_index.intersects(geom.boundingBox())
            for pid in candidate_ids:
                buf_geom = buzt_geometrie.get(pid)
                if not buf_geom:
                    continue
                intersect = geom.intersection(buf_geom)
                if intersect and not intersect.isEmpty():
                    nowy_feat = QgsFeature()
                    nowy_feat.setFields(layer.fields())
                    nowy_feat.setGeometry(intersect)
                    nowy_feat.initAttributes(len(layer.fields()))
                    for i in range(len(layer.fields())):
                        nowy_feat.setAttribute(i, feat[i])
                    gml_idx = layer.fields().indexFromName("gml_id")
                    if gml_idx >= 0:
                        nowy_feat.setAttribute(gml_idx, str(feat["gml_id"]))
                    obiektyZbledami.append(nowy_feat)
                    break
        return obiektyZbledami
    except Exception as e:
        print("Błąd w funkcji przecinanieRTLW_BUZT:", e)
        return []




def boundaryPTWP_poziomica(layer):
    """
    Kontrola przecięcia poziomic (RTLW_L) z wodami powierzchniowymi (PTWP_A).
    Poziomice mogą przecinać wodę płynącą, ale nie mogą przecinać wody stojącej.
    Sprawdzane są tylko poziomice z wypełnionym kodem Karto10k
    """    
    try:
        obiektyZbledami = []
        if not layer or not layer.isValid() or not layer.name().endswith("OT_RTLW_L"):
            return []
        ptwp_layer = next((l for l in QgsProject.instance().mapLayers().values()
                           if l.name().endswith("OT_PTWP_A")), None)
        if not ptwp_layer or not ptwp_layer.isValid():
            return []
        ptwp_index = QgsSpatialIndex()
        ptwp_geometrie = {}
        for feat in ptwp_layer.getFeatures():
            rodzaj = str(feat["rodzaj"]).strip().lower()
            if rodzaj != "woda płynąca":
                geom = feat.geometry()
                if geom and not geom.isEmpty():
                    buf_geom = geom.buffer(-0.02, 8)
                    if buf_geom and not buf_geom.isEmpty():
                        ptwp_index.insertFeature(feat)
                        ptwp_geometrie[feat.id()] = buf_geom
        kod_idx = layer.fields().indexFromName("kodKarto10k")
        for feat in layer.getFeatures():
            rodzaj = str(feat["rodzaj"]).strip().lower() if "rodzaj" in feat.fields().names() else ""
            if rodzaj != "poziomica":
                continue
            kod_karto = ""
            if kod_idx >= 0:
                val = feat.attribute(kod_idx)
                if val not in (None, "", " ", "null", "NULL"):
                    kod_karto = str(val).strip()
            if not kod_karto:
                continue
            geom = feat.geometry()
            if not geom or geom.isEmpty():
                continue
            candidate_ids = ptwp_index.intersects(geom.boundingBox())
            if not candidate_ids:
                continue
            for pid in candidate_ids:
                buf_geom = ptwp_geometrie.get(pid)
                if not buf_geom:
                    continue
                if not geom.boundingBox().intersects(buf_geom.boundingBox()):
                    continue
                intersect = geom.intersection(buf_geom)
                if not intersect or intersect.isEmpty():
                    continue
                if hasattr(intersect, "length") and intersect.length() > 0:
                    nowy_feat = QgsFeature()
                    nowy_feat.setFields(layer.fields())
                    nowy_feat.setGeometry(intersect)
                    nowy_feat.initAttributes(len(layer.fields()))
                    for i in range(len(layer.fields())):
                        try:
                            nowy_feat.setAttribute(i, feat.attribute(i))
                        except Exception:
                            nowy_feat.setAttribute(i, None)
                    gml_idx = layer.fields().indexFromName("gml_id")
                    if gml_idx >= 0:
                        nowy_feat.setAttribute(gml_idx, str(feat.attribute(gml_idx) or ""))
                    obiektyZbledami.append(nowy_feat)
                    break
        return obiektyZbledami

    except Exception as e:
        print("Błąd w funkcji boundaryPTWP_poziomica:", e)
        return []


def kontrolaZdublowaniaAtrybutuFunkcjaSzczegolowaBudynku(layer):
    obiektyZbledami = []
    idenBledow = set()
    for feature in layer.getFeatures():
        funkcje_szczegolowe = feature['funkcjaSzczegolowaBudynku'] 
        if isinstance(funkcje_szczegolowe, list):
           wystapienia = {}
           for funkcja in funkcje_szczegolowe:
                if funkcja in wystapienia:
                    wystapienia[funkcja] += 1
                else:
                    wystapienia[funkcja] = 1
            # Sprawdzenie, czy występują powtórzenia (wartość większa niż 1)
           powtorzenia = [key for key, count in wystapienia.items() if count > 1]
           if powtorzenia and feature.id() not in idenBledow:
                obiektyZbledami.append(feature)
                idenBledow.add(feature.id())
    return obiektyZbledami


def kontrolaZgodnosciFunkcjaSzczegolowaBudynkuZprzewazajacaFunkcjaBudynku(layer):
    obiektyZbledami = []
    idenBledow = set()
    
    for feature in layer.getFeatures():
        if "funkcjaSzczegolowaBudynku" not in feature.fields().names() or "przewazajacaFunkcjaBudynku" not in feature.fields().names():
            continue
        
        funkcje_szczegolowe = feature.attribute("funkcjaSzczegolowaBudynku")
        funkcja_przewazajaca = feature.attribute("przewazajacaFunkcjaBudynku")
        
        if not funkcje_szczegolowe or not funkcja_przewazajaca:
            continue
            
        if not isinstance(funkcje_szczegolowe, (list, set)):
            funkcje_szczegolowe = {funkcje_szczegolowe}
        else:
            funkcje_szczegolowe = set(funkcje_szczegolowe)
            
        if funkcja_przewazajaca not in funkcje_szczegolowe:
            if feature.id() not in idenBledow:
                obiektyZbledami.append(feature)
                idenBledow.add(feature.id())
                
    return obiektyZbledami


def KontrolaAtrybutuGeometriaBudynek(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'egb': 'ewidencjaGruntowIBudynkow:1.0'}
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces = ns):
        budynek = featureMember.find('.//egb:EGB_ObiektTrwaleZwiazanyZBudynkiem', namespaces=ns)
        if budynek is not None:
            rodzajObiektu = budynek.find('.//egb:rodzajObiektuZwiazanegoZBudynkiem', namespaces=ns)
            rodzajObiektu_text = rodzajObiektu.text if rodzajObiektu is not None else "Nieznane ID"
            if rodzajObiektu_text in ['t', 'w', 'i','s','r','j','d']:
                lokalnyId = budynek.find('.//egb:lokalnyId', namespaces=ns)
                lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
                geometria = budynek.findall('.//egb:geometria', namespaces=ns)
                for g in geometria:
                    polygon = g.find('.//gml:Polygon', namespaces=ns)
                    surface = g.find('.//gml:Surface', namespaces=ns)
                    if polygon is None or surface is None:
                        expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                        request = QgsFeatureRequest().setFilterExpression(expression)
                        for feature in layer.getFeatures(request):
                           obiektyZbledami.append(feature)
                           
    return obiektyZbledami


def KontrolaAtrybutuKlasouzytek(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'egb': 'ewidencjaGruntowIBudynkow:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces = ns):
        dzialka = featureMember.find('.//egb:EGB_DzialkaEwidencyjna', namespaces = ns)
        if dzialka is not None:
           # Dla każdego Klasouzytek w obrębie Działki Ewidencyjnej
           for klasouzytek in dzialka.findall('.//egb:EGB_Klasouzytek', namespaces = ns):
               lokalnyId = dzialka.find('.//egb:lokalnyId', namespaces=ns)
               lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
               OFU = klasouzytek.find('.//egb:OFU', namespaces=ns)
               ofu_value = OFU.text if OFU is not None else None
               if ofu_value in ['Ls', 'W']:
                   OZU = klasouzytek.find('.//egb:OZU', namespaces=ns)
                   ozu_value = OZU.text if OZU is not None else None
                   if ozu_value == 'Ls':
                       OZK = klasouzytek.find('.//egb:OZK', namespaces=ns)
                       ozk_value = OZK.text if OZK is not None else None
                       if ozk_value in ['IIIa', 'IIIb', 'IVa', 'IVb']:
                           expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                           request = QgsFeatureRequest().setFilterExpression(expression)
                           for feature in layer.getFeatures(request):
                               obiektyZbledami.append(feature)
    
    return obiektyZbledami


def KontrolaAtrybutuKlasouzytek6(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'egb': 'ewidencjaGruntowIBudynkow:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces = ns):
        dzialka = featureMember.find('.//egb:EGB_DzialkaEwidencyjna', namespaces = ns)
        if dzialka is not None:
           # Dla każdego Klasouzytek w obrębie Działki Ewidencyjnej
           for klasouzytek in dzialka.findall('.//egb:EGB_Klasouzytek', namespaces = ns):
               lokalnyId = dzialka.find('.//egb:lokalnyId', namespaces=ns)
               lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
               OFU = klasouzytek.find('.//egb:OFU', namespaces=ns)
               ofu_value = OFU.text if OFU is not None else None
               if ofu_value in ['Lz', 'W']:
                   OZU = klasouzytek.find('.//egb:OZU', namespaces=ns)
                   ozu_value = OZU.text if OZU is not None else None
                   if ozu_value == 'Lz':
                       OZK = klasouzytek.find('.//egb:OZK', namespaces=ns)
                       ozk_value = OZK.text if OZK is not None else None
                       if ozk_value in ['IIIa', 'IIIb', 'IVa', 'IVb']:
                           expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                           request = QgsFeatureRequest().setFilterExpression(expression)
                           for feature in layer.getFeatures(request):
                               obiektyZbledami.append(feature)
    
    return obiektyZbledami


def KontrolaAtrybutuKlasouzytek2(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'egb': 'ewidencjaGruntowIBudynkow:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        dzialka = featureMember.find('.//egb:EGB_DzialkaEwidencyjna', namespaces=ns)
        if dzialka is not None:
           # Dla każdego Klasouzytek w obrębie Działki Ewidencyjnej
           for klasouzytek in dzialka.findall('.//egb:EGB_Klasouzytek', namespaces=ns):
               lokalnyId = dzialka.find('.//egb:lokalnyId', namespaces=ns)
               lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
               OFU = klasouzytek.find('.//egb:OFU', namespaces=ns)
               ofu_value = OFU.text if OFU is not None else None
               if ofu_value in ['R','S', 'Br','Wsr','W','Lzr']:
                   OZU = klasouzytek.find('.//egb:OZU', namespaces=ns)
                   ozu_value = OZU.text if OZU is not None else None
                   if ozu_value == 'R':
                       OZK = klasouzytek.find('.//egb:OZK', namespaces=ns)
                       ozk_value = OZK.text if OZK is not None else None
                       if not ozk_value in ['I','II','IIIa', 'IIIb', 'IVa', 'IVb','V','VI','VIz']:
                           expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                           request = QgsFeatureRequest().setFilterExpression(expression)
                           for feature in layer.getFeatures(request):
                               obiektyZbledami.append(feature)
    
    return obiektyZbledami


def KontrolaAtrybutuKlasouzytek3(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'egb': 'ewidencjaGruntowIBudynkow:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        dzialka = featureMember.find('.//egb:EGB_DzialkaEwidencyjna', namespaces=ns)
        if dzialka is not None:
           # Dla każdego Klasouzytek w obrębie Działki Ewidencyjnej
           for klasouzytek in dzialka.findall('.//egb:EGB_Klasouzytek', namespaces=ns):
               lokalnyId = dzialka.find('.//egb:lokalnyId', namespaces=ns)
               lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
               OFU = klasouzytek.find('.//egb:OFU', namespaces=ns)
               ofu_value = OFU.text if OFU is not None else None
               if ofu_value in ['Ł','S','Br','Wsr','W','Lzr']:
                   OZU = klasouzytek.find('.//egb:OZU', namespaces=ns)
                   ozu_value = OZU.text if OZU is not None else None
                   if ozu_value == 'Ł':
                       OZK = klasouzytek.find('.//egb:OZK', namespaces=ns)
                       ozk_value = OZK.text if OZK is not None else None
                       if not ozk_value in ['I','II','III', 'IV','V','VI']:
                           expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                           request = QgsFeatureRequest().setFilterExpression(expression)
                           for feature in layer.getFeatures(request):
                               obiektyZbledami.append(feature)
    
    return obiektyZbledami


def KontrolaAtrybutuKlasouzytek4(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'egb': 'ewidencjaGruntowIBudynkow:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        dzialka = featureMember.find('.//egb:EGB_DzialkaEwidencyjna', namespaces=ns)
        if dzialka is not None:
           # Dla każdego Klasouzytek w obrębie Działki Ewidencyjnej
           for klasouzytek in dzialka.findall('.//egb:EGB_Klasouzytek', namespaces=ns):
               lokalnyId = dzialka.find('.//egb:lokalnyId', namespaces=ns)
               lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
               OFU = klasouzytek.find('.//egb:OFU', namespaces=ns)
               ofu_value = OFU.text if OFU is not None else None
               if ofu_value in ['Ps','S','Br','Wsr','W','Lzr']:
                   OZU = klasouzytek.find('.//egb:OZU', namespaces=ns)
                   ozu_value = OZU.text if OZU is not None else None
                   if ozu_value == 'Ps':
                       OZK = klasouzytek.find('.//egb:OZK', namespaces=ns)
                       ozk_value = OZK.text if OZK is not None else None
                       if not ozk_value in ['I','II','III', 'IV','V','VI']:
                           expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                           request = QgsFeatureRequest().setFilterExpression(expression)
                           for feature in layer.getFeatures(request):
                               obiektyZbledami.append(feature)
    
    return obiektyZbledami

def kontrolaGeometriaOtWody(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'ot': 'bazaDanychObiektowTopograficznych500:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        ot_wody = featureMember.find('.//ot:OT_Wody', namespaces=ns)
        
        if ot_wody is not None:
            rodzajObiektu = ot_wody.find('.//ot:rodzajObiektu', namespaces=ns)
            rodzajObiektu_text = rodzajObiektu.text if rodzajObiektu is not None else "Nieznane ID"
            
            if rodzajObiektu_text in ['p','s','j','l','z','t','o']:
                ot_geometria =  ot_wody.findall('.//ot:geometria', namespaces=ns)
                lokalnyId = ot_wody.find('.//ot:lokalnyId', namespaces=ns)
                lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
                expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                request = QgsFeatureRequest().setFilterExpression(expression)
                
                if not ot_geometria: # całkowity brak atrybutu
                    for feature in layer.getFeatures(request):
                        obiektyZbledami.append(feature)
                else: # obiekt posiada niepoprawną geometrię w atrybucie
                    
                    for g in ot_geometria:
                        # QgsMessageLog.logMessage(f"3.g:\t{g}", "Walidator", level=Qgis.Info)
                        isValid = False
                        validGeometry = ['Polygon', 'Surface'] # akceptowalna geometria
                        for vg in validGeometry:
                            if g.find(f'.//gml:{vg}', namespaces=ns) is not None:
                                isValid = True
                                break # wyjście jeśli znajdzie poprawny obiekt
                        if isValid: continue
                        request = QgsFeatureRequest().setFilterExpression(expression)
                        for feature in layer.getFeatures(request):
                            obiektyZbledami.append(feature)
    
    return obiektyZbledami


def kontrolaGeometriaOtWody2(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'ot': 'bazaDanychObiektowTopograficznych500:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        ot_wody = featureMember.find('.//ot:OT_Wody', namespaces=ns)
        
        if ot_wody is not None:
            rodzajObiektu = ot_wody.find('.//ot:rodzajObiektu', namespaces=ns)
            rodzajObiektu_text = rodzajObiektu.text if rodzajObiektu is not None else "Nieznane ID"
            
            if rodzajObiektu_text in ['w','g']:
                ot_geometria =  ot_wody.findall('.//ot:geometria', namespaces=ns)
                lokalnyId = ot_wody.find('.//ot:lokalnyId', namespaces=ns)
                lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
                expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                request = QgsFeatureRequest().setFilterExpression(expression)
                
                if not ot_geometria: # całkowity brak atrybutu
                    for feature in layer.getFeatures(request):
                        obiektyZbledami.append(feature)
                else: # obiekt posiada niepoprawną geometrię w atrybucie
                    for g in ot_geometria:
                        isValid = False
                        validGeometry = ['Polygon', 'Surface', 'MultiSurface'] # akceptowalna geometria
                        for vg in validGeometry:
                            if g.find(f'.//gml:{vg}', namespaces=ns) is not None:
                                isValid = True
                                break # wyjście jeśli znajdzie poprawny obiekt
                        if isValid: continue
                        request = QgsFeatureRequest().setFilterExpression(expression)
                        for feature in layer.getFeatures(request):
                            obiektyZbledami.append(feature)
    
    return obiektyZbledami


def KontrolaGeometriaSchody(layer, plikGML):
    obiektyZbledami = []
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'egb': 'ewidencjaGruntowIBudynkow:1.0'}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        budynek = featureMember.find('.//egb:EGB_ObiektTrwaleZwiazanyZBudynkiem', namespaces=ns)
        if budynek is not None:
            rodzajObiektu = budynek.find('.//egb:rodzajObiektuZwiazanegoZBudynkiem', namespaces=ns)
            rodzajObiektu_text = rodzajObiektu.text if rodzajObiektu is not None else "Nieznane ID"
            if rodzajObiektu_text == 's': # tylko schody
                polKier =  budynek.findall('.//egb:poliliniaKierunkowa', namespaces=ns)
                lokalnyId = budynek.find('.//egb:lokalnyId', namespaces=ns)
                lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
                expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                request = QgsFeatureRequest().setFilterExpression(expression)
                if not polKier: # całkowity brak atrybutu
                    for feature in layer.getFeatures(request):
                        obiektyZbledami.append(feature)
                else: # schody nie posiadają poprawnej lini kierunkowej
                    for pk in polKier:
                        posList = pk.find('.//gml:posList', namespaces=ns)
                        pos = pk.find('.//gml:pos', namespaces=ns)
                        if pos is not None: # punkt
                            pos_wsp = pos.text 
                            if pos_wsp is None:
                                for feature in layer.getFeatures(request):
                                    obiektyZbledami.append(feature)
                        if posList is not None: # linie i powierzchnie
                            posList_wsp = posList.text
                            if posList_wsp is None:
                                for feature in layer.getFeatures(request):
                                    obiektyZbledami.append(feature)
    
    return obiektyZbledami


def gminyCzyNakladajaSie(layer):
    obiektyZbledami = []  
    gm = processing.run("native:extractbyexpression", {
        'INPUT':layer,
        'EXPRESSION':'"rodzaj" = \'gmina\'',
        'OUTPUT':'memory:'
    })
    gminy = gm['OUTPUT']   
    invalid_layer = processing.run("qgis:checkvalidity", {
        'INPUT_LAYER': gminy,
        'METHOD': 0, 
        'IGNORE_RING_SELF_INTERSECTION': True,
        'VALID_OUTPUT': 'memory:',
        'INVALID_OUTPUT': 'memory:',
        'ERROR_OUTPUT': 'memory:'
    })   
    # zawiera warstwę z niepoprawnymi geometriami
    if invalid_layer['INVALID_OUTPUT']:
              # Wykonanie naprawy geometrii
              naprawiona = processing.run("native:fixgeometries", {
                  'INPUT': gminy,
                  'OUTPUT': 'memory:'
              })
              gminy = naprawiona['OUTPUT']
    
    union = processing.run("native:union", {
        'INPUT': gminy,
        'OUTPUT': 'memory:'
    })   
    multiparttosingleparts = processing.run("native:multiparttosingleparts", {
        'INPUT': union['OUTPUT'],
        'OUTPUT': 'memory:'
    })    
    geom_union = []
    for f in multiparttosingleparts['OUTPUT'].getFeatures():
        g = f.geometry()
        geom_wkt = g.asWkt()
        if not geom_wkt in geom_union:
            geom_union.append(geom_wkt)
        else:
            if f.geometry().area() > 2:
                obiektyZbledami.append(f)
    
    return obiektyZbledami


def miastoWiesCzyNakladajaSie(layer):
    obiektyZbledami = []  
    extractbyexpression = processing.run("native:extractbyexpression",{
        'INPUT': layer,
        'EXPRESSION':'"rodzaj"=\'miasto\' or "rodzaj"=\'wieś\'',
        'OUTPUT':'memory:'})
    miastoWies = extractbyexpression['OUTPUT']
    miastoWies.setName("miasto_wies")   
    checkvalidity = processing.run("qgis:checkvalidity",{
        'INPUT_LAYER': miastoWies,
        'METHOD': 0,
        'IGNORE_RING_SELF_INTERSECTION': True,
        'VALID_OUTPUT': 'memory:',
        'INVALID_OUTPUT': 'memory:',
        'ERROR_OUTPUT': 'memory:'
    })   
    # zawiera warstwę z niepoprawnymi geometriami
    if checkvalidity['INVALID_OUTPUT']:
              # Wykonanie naprawy geometrii
              naprawiona = processing.run("native:fixgeometries", {
                  'INPUT': miastoWies,
                  'OUTPUT': 'memory:'
              })
              miastoWies = naprawiona['OUTPUT']    
    union = processing.run("native:union", {
        'INPUT': miastoWies,
        'OUTPUT': 'memory:'
    })    
    multiparttosingleparts = processing.run("native:multiparttosingleparts", {
        'INPUT': union['OUTPUT'],
        'OUTPUT': 'memory:'
    })    
    geom_union = []
    for f in multiparttosingleparts['OUTPUT'].getFeatures():
        g = f.geometry()
        geom_wkt = g.asWkt()
        if not geom_wkt in geom_union:
            geom_union.append(geom_wkt)
        else:
            if f.geometry().area() > 2:
                obiektyZbledami.append(f)
    
    return obiektyZbledami


def sprawdzLokalnyId(layer):
    obiektyZbledami = []
    if layer.name()[-6:] in ['RTLW_L', 'RTPW_P']:
        return obiektyZbledami
    uuid_pattern = re.compile(r'^[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}$')
    unique_ids = set()
    for f in layer.getFeatures():
        lokalny = f.attribute('lokalnyId')
        try:
            lokalny_str = str(lokalny)
        except Exception:
            obiektyZbledami.append(f)
            continue
        if not uuid_pattern.match(lokalny_str):
            obiektyZbledami.append(f)
            continue
        if lokalny_str in unique_ids:
            obiektyZbledami.append(f)
        else:
            unique_ids.add(lokalny_str)
    return obiektyZbledami


def sprawdzPrzestrzenNazw(layer):
    obiektyZbledami = []
    if layer.name()[-6:] in ['RTLW_L', 'RTPW_P']:
        return obiektyZbledami
        
    dozwolone_kody = {
        '02': '337',  # dolnośląskie
        '04': '994',  # kujawsko-pomrskie
        '06': '3700', # lubelskie
        '26': '370',  # świętokrzyskie
        '08': '333',  # lubuskie
        '10': '340',  # łódzkie
        '12': '283',  # małopolskie
        '14': '330',  # mazowieckie
        '16': '1833', # opolskie
        '18': '332',  # podlaskie
        '20': '335',  # podkarpackie
        '22': '336',  # pomorskie
        '24': '238',  # śląskie
        '28': '341',  # warmińsko-mazurskie
        '30': '308',  # wielkopolskie
        '32': '339'   # zachodniopomorskie
    }
    pattern = re.compile(r'^PL\.PZGiK\.(\d{3,4})\.BDOT10k$')
    for f in layer.getFeatures():
        przestrzen = f.attribute('przestrzenNazw')
        try:
            przestrzen_str = str(przestrzen)
        except Exception:
            obiektyZbledami.append(f)
            continue
        match = pattern.match(przestrzen_str)
        if not match:
            obiektyZbledami.append(f)
            continue
        woj = layer.name()[13:15]
        if woj in ['.1', '.0']:
            woj = layer.name()[14:16]
        cyfry = match.group(1) if match.group(1) else None
        expected_cyfry = dozwolone_kody.get(woj, None)
        
        valid = cyfry == expected_cyfry if expected_cyfry else False
        if not valid:
            obiektyZbledami.append(f)
            
    return obiektyZbledami


def sprawdzWersja(layer):
    obiektyZbledami = []
    if layer.name()[-6:] in ['RTLW_L', 'RTPW_P']:
        # funkcja nie wykona się dla warstw z rzeźbą terenu        
        return obiektyZbledami        
    uuid_pattern = re.compile(r'^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]$|^$')
    unique_ids = set()
    for f in layer.getFeatures():
        wersja = f.attribute('wersja')
        wersja_str = str(wersja) if wersja is not None else ''
        if isinstance(wersja_str, str):
            matches_pattern = bool(uuid_pattern.match(wersja_str))
            if wersja_str not in unique_ids and not matches_pattern:
                unique_ids.add(wersja_str)
                obiektyZbledami.append(f)
        else:
            obiektyZbledami.append(f)

    return obiektyZbledami


def sprawdzPoczatekWersjiObiektu(layer):
    obiektyZbledami = []
    if layer.name()[-6:] in ['RTLW_L', 'RTPW_P']:
        # funkcja nie wykona się dla warstw z rzeźbą terenu
        return obiektyZbledami
    uuid_pattern = re.compile(r'^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]$')
    # Zbiór do sprawdzania unikalności
    unique_ids = set()
    for f in layer.getFeatures():
        poczatek = f.attribute('poczatekWersjiObiektu')
        wersja = f.attribute('wersja')
        if not isinstance(wersja, str):
            wersja = str(wersja)
        if not isinstance(poczatek, str):
            poczatek = str(poczatek) # konwersja pustej wartosci na string
        if wersja != poczatek and f.id() not in unique_ids:
             unique_ids.add(f.id())
             obiektyZbledami.append(f)
        if not uuid_pattern.match(wersja) and f.id() not in unique_ids:
             unique_ids.add(f.id())
             obiektyZbledami.append(f)
        if not uuid_pattern.match(poczatek) and f.id() not in unique_ids:
             unique_ids.add(f.id())
             obiektyZbledami.append(f)
    
    return obiektyZbledami


def przestrzenNazw(layer,teryt):
    obiektyZbledami = []
    slownik = {
    '02': '337',  # dolnośląskie
    '04': '994',  # kujawsko-pomrskie
    '06': '3700', # lubelskie
    '08': '333',  # lubuskie
    '10': '340',  # łódzkie
    '12': '283',  # małopolskie
    '14': '330',  # mazowieckie
    '16': '1833', # opolskie
    '18': '332',  # podlaskie
    '20': '335',  # podkarpackie
    '22': '336',  # pomorskie
    '24': '238',  # śląskie
    '26': '370',  # świętokrzyskie
    '28': '341',  # warmińsko-mazurskie
    '30': '308',  # wielkopolskie
    '32': '339'   # zachodniopomorskie
    }
    pattern = re.compile(r'^PL\.PZGiK\.(\d{3,4})\.BDOT10k$')
    # Zbiór do sprawdzania unikalności
    unique_ids = set()
    for f in layer.getFeatures():
        przestrzen = f.attribute('przestrzenNazw')
        if not isinstance(przestrzen, str):
            przestrzen = str(przestrzen) # konwersja pustej wartosci na string
            obiektyZbledami.append(f)
        else:
            match = pattern.match(przestrzen)
            if not match: # sprawdza, czy są niezgodne z schematem
                match = pattern.match(przestrzen)
                obiektyZbledami.append(f)
            else:
                woj = teryt[:2]
                cyfry = match.group(1) if match.group(1) else None
                expected_cyfry = slownik.get(woj, None)
                if isinstance(expected_cyfry, list):
                    valid = cyfry in expected_cyfry
                else:
                    valid = cyfry == expected_cyfry
                if not valid:
                    obiektyZbledami.append(f)
    
    return obiektyZbledami


def zapisWspolrzednych(layer, plikGML):
    """
    Kontrola zapisu geometrii z wykrywaniem błędów geometrycznych typu spike, niezamknięty poligon, nieparzysta liczba współrzędnych,
    niewystarczająca liczba punktów dla linii, błędny format współrzędnych.
    """  
    try:
        obiektyZbledami = []
        badfMember_Z_dic = {}
        root = plikGML.getroot()
        otklasa = f'.//ot:{layer.name()[-9:]}'
        ns = {'gml': 'http://www.opengis.net/gml/3.2', 'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'}
        wsp_pattern = re.compile(r'^[1-9][0-9]{5}\.[0-9]{2}$')
        def init_layer_dict(attr_name):
            return {"Polygon": QgsVectorLayer("None?crs=EPSG:2180", attr_name + "_A", "memory"),
                    "LineString": QgsVectorLayer("None?crs=EPSG:2180", attr_name + "_L", "memory"),
                    "Point": QgsVectorLayer("None?crs=EPSG:2180", attr_name + "_P", "memory")}
        if not hasattr(zapisWspolrzednych, "bledy_krytyczne_warstwy"):
            zapisWspolrzednych.bledy_krytyczne_warstwy = init_layer_dict("Błędy_krytyczne_walidacji_geometrii")
            for warstwa in zapisWspolrzednych.bledy_krytyczne_warstwy.values():
                warstwa.dataProvider().addAttributes([
                    QgsField("gml_id", QVariant.String),
                    QgsField("nazwaKlasy", QVariant.String),
                    QgsField("trescBledu", QVariant.String)])
                warstwa.updateFields()
        feats_k = {k: [] for k in ["Polygon", "LineString", "Point"]}
        klasy_krytyczne = set()
        wszystkie_feature = list(layer.getFeatures())
        lokalnyId_to_feature = {f["lokalnyId"]: f for f in wszystkie_feature}
        fields = layer.fields()
        idx_gml_id = fields.indexFromName("gml_id")
        def _bufor():
            if hasattr(zapisWspolrzednych,"powiat_bufor_minus2cm"): return zapisWspolrzednych.powiat_bufor_minus2cm
            try:
                cfg = configparser.ConfigParser()
                mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath())/"python/plugins/Walidator_plikow_gml"
                cfg.read(str(mainPath/"Walidator_plikow_gml.ini"))
                granicePowiatow = QgsVectorLayer(cfg['DEFAULT']['granicepowiatow'],'GranicePowiatow','ogr')
                m = re.search(r"\.(\d{4})__",layer.name())
                if not m: zapisWspolrzednych.powiat_bufor_minus2cm=None; return None
                t = m.group(1)
                geom = next((f.geometry() for f in granicePowiatow.getFeatures() if str(f['JPT_KOD_JE'])==t),None)
                zapisWspolrzednych.powiat_bufor_minus2cm = geom.buffer(-0.02,5) if geom else None
                return zapisWspolrzednych.powiat_bufor_minus2cm
            except: zapisWspolrzednych.powiat_bufor_minus2cm=None; return None
        powiat_bufor_minus2cm = _bufor()        
        for featureMember in root.findall('.//gml:featureMember', namespaces=ns):
            matches = featureMember.find(otklasa, namespaces=ns)
            if matches is None:
                continue
            lokalnyId = matches.findtext('.//ot:lokalnyId', default=None, namespaces=ns)
            gml_id = matches.attrib.get("{http://www.opengis.net/gml/3.2}id", "Brak")
            geometrie = matches.findall('.//ot:geometria', namespaces=ns)
            wszystkie_bledy, typ_geom = [], None
            for g in geometrie:
                lista_bledow = []
                for el in g.findall(".//gml:pos", ns) + g.findall(".//gml:posList", ns):
                    if not el.text or not el.text.strip():
                        lista_bledow.append(f"pusty element {el.tag.split('}')[1]}")
                if lista_bledow:
                    wszystkie_bledy.extend(lista_bledow)
                    continue
                geomPkt = g.find(".//gml:pos", ns)
                geomList = g.find(".//gml:posList", ns)
                wsp_list = []
                if geomPkt is not None and geomPkt.text.strip():
                    wsp_list = geomPkt.text.strip().split()
                    typ_geom = "Point"
                elif geomList is not None and geomList.text.strip():
                    wsp_list = geomList.text.strip().split()                  
                    # Linie
                    if g.find('.//gml:LineString', namespaces=ns) is not None \
                       or g.find('.//gml:Curve', namespaces=ns) is not None:
                        typ_geom = "LineString"
                        if len(wsp_list) < 4:
                            lista_bledow.append("niewystarczająca liczba punktów dla linii")                   
                    # Poligony
                    elif g.find('.//gml:Polygon', namespaces=ns) is not None:
                        typ_geom = "Polygon"
                        if len(wsp_list) < 8 or wsp_list[0] != wsp_list[-2] or wsp_list[1] != wsp_list[-1]:
                            lista_bledow.append("niezamknięty poligon")
                if wsp_list:
                    if any(not wsp_pattern.match(coord) for coord in wsp_list):
                        lista_bledow.append("błędny format współrzędnych")
                    if len(wsp_list) % 2 != 0:
                        lista_bledow.append("nieparzysta liczba współrzędnych")
                koniec_wersji = matches.find('.//ot:koniecWersjiObiektu', ns)
                if wsp_list and typ_geom in ("LineString","Polygon") and koniec_wersji is None:
                    try:
                        geom_pts = [(float(wsp_list[i]), float(wsp_list[i+1])) for i in range(0, len(wsp_list), 2)]
                        if typ_geom=="Polygon" and geom_pts[0]==geom_pts[-1]: geom_pts=geom_pts[:-1]
                        seg_lengths = [sqrt((geom_pts[i+1][0]-geom_pts[i][0])**2 + (geom_pts[i+1][1]-geom_pts[i][1])**2) for i in range(len(geom_pts)-1)]
                        avg_len = sum(seg_lengths)/len(seg_lengths) if seg_lengths else 0
                        spikes = []
                        spike_points = []
                  # ustawienie kąta zależnie od warstwy
                        if layer.name().endswith("OT_BUBD_A"):
                            spike_angle_threshold = 10
                        else:
                            spike_angle_threshold = 3
                        for i in range(1, len(geom_pts)-1):
                            x1,y1 = geom_pts[i-1]; x2,y2 = geom_pts[i]; x3,y3 = geom_pts[i+1]
                            len1 = sqrt((x2-x1)**2 + (y2-y1)**2)
                            len2 = sqrt((x3-x2)**2 + (y3-y2)**2)
                            v1 = (x1-x2, y1-y2); v2 = (x3-x2, y3-y2)
                            cosang = max(-1, min(1, (v1[0]*v2[0] + v1[1]*v2[1])/(len1*len2) if len1*len2>0 else 1))
                            angle = degrees(acos(cosang))
                            if angle <= spike_angle_threshold and (len1 >= 0.01 or len2 >= 0.01):
                                spikes.append(i)
                                f_spike = QgsFeature()
                                f_spike.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x2, y2)))
                                f_spike.setAttributes([gml_id, lokalnyId, layer.name(), f"błąd typu spike (indeksy: {i})"])
                                spike_points.append(f_spike)
                        if spike_points:
                            if powiat_bufor_minus2cm:
                                spike_points=[p for p in spike_points if p.geometry() and p.geometry().within(powiat_bufor_minus2cm)]
                            if spike_points:
                                lista_bledow.append(f"błąd typu spike (indeksy: {','.join(map(str,spikes))})")
                                if not hasattr(zapisWspolrzednych, "spike_layer_all"):
                                    zapisWspolrzednych.spike_layer_all = QgsVectorLayer("Point?crs=EPSG:2180", "spike_errors_all", "memory")
                                    zapisWspolrzednych.spike_layer_all.dataProvider().addAttributes([
                                        QgsField("gml_id", QVariant.String),
                                        QgsField("lokalnyId", QVariant.String),
                                        QgsField("nazwaKlasy", QVariant.String),
                                        QgsField("trescBledu", QVariant.String)])
                                    zapisWspolrzednych.spike_layer_all.updateFields()
                                    QgsProject.instance().addMapLayer(zapisWspolrzednych.spike_layer_all)
                                zapisWspolrzednych.spike_layer_all.dataProvider().addFeatures(spike_points)
                                zapisWspolrzednych.spike_layer_all.updateExtents()
                    except Exception as ee:
                        lista_bledow.append(f"błąd detekcji spike: {ee}") 
                if lista_bledow:
                    wszystkie_bledy.extend(lista_bledow)
            if wszystkie_bledy:
                bledy_unikalne = sorted(set(wszystkie_bledy))
                trescBledu = '|'.join(bledy_unikalne)
                bledy_bez_formatu = [b for b in bledy_unikalne if b != "błędny format współrzędnych"]
                if lokalnyId in lokalnyId_to_feature:
                    badfMember_Z_dic.setdefault(lokalnyId, []).append(trescBledu)
                if bledy_bez_formatu and typ_geom and not any("spike" in b for b in bledy_bez_formatu):
                    feat_kryt = QgsFeature(zapisWspolrzednych.bledy_krytyczne_warstwy[typ_geom].fields())
                    feat_kryt.setAttribute("gml_id", gml_id)
                    feat_kryt.setAttribute("nazwaKlasy", layer.name())
                    feat_kryt.setAttribute("trescBledu", trescBledu)
                    feat_kryt.setGeometry(None)
                    feats_k[typ_geom].append(feat_kryt)
                    if lokalnyId not in lokalnyId_to_feature and layer.name() not in klasy_krytyczne:
                        dummy = QgsFeature(fields)
                        dummy.setAttribute("gml_id", f"{gml_id}|błędy krytyczne walidacji geometrii")
                        dummy.setAttribute("lokalnyId", lokalnyId or "nie dotyczy")
                        obiektyZbledami.append(dummy)
                        klasy_krytyczne.add(layer.name())
        layer.startEditing()
        layer.beginEditCommand("Aktualizacja gml_id z błędami")
        for feature in wszystkie_feature:
            lokalnyId = feature["lokalnyId"]
            if lokalnyId in badfMember_Z_dic:
                komunikat = '|'.join(sorted(set(badfMember_Z_dic[lokalnyId])))
                nowy_gml_id = f"{feature['gml_id']}|{komunikat}"
                feature.setAttribute(idx_gml_id, nowy_gml_id)
                layer.updateFeature(feature)
                obiektyZbledami.append(feature)
        layer.endEditCommand()
        layer.commitChanges()
        for typ in ["Polygon", "LineString", "Point"]:
            zbior = feats_k[typ]
            if zbior:
                warstwa = zapisWspolrzednych.bledy_krytyczne_warstwy[typ]
                warstwa.dataProvider().addFeatures(zbior)
                warstwa.updateExtents()
                QgsProject.instance().addMapLayer(warstwa)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w zapisWspolrzednych: {e}")
        return []



def minDlugoscOIPR(layer):
    obiektyZbledami = []
    if not layer.name().endswith('OT_OIPR_L'):
        return obiektyZbledami   
    layers = QgsProject().instance().mapLayersByName(layer.name())
    if not layers:
        return obiektyZbledami   
    OIPR_L_layer = layers[0]
    granica = adjaMinus2cmBufor(layer)    
    for OIPR_L_feature in OIPR_L_layer.getFeatures():
        geom = OIPR_L_feature.geometry()
        if geom and geom.length() < 40 and OIPR_L_feature['rodzaj'] in ['rząd drzew','pas krzewów lub żywopłot']:
            for g in granica.getFeatures():
                if not geom.intersects(g.geometry()):
                    obiektyZbledami.append(OIPR_L_feature)
                    break
    return obiektyZbledami 
    


def minDlugoscBUUO(layer):
    try:
        if not layer.isValid():
            return []
        obiektyZbledami = []
        layer_name = layer.name()
        if not layer_name.endswith("BUUO_L"):
            return []
        BUUO_L_layers = QgsProject.instance().mapLayersByName(layer_name)
        if not BUUO_L_layers:
            return []
        BUUO_L_layer = BUUO_L_layers[0]
        buuo_features = list(BUUO_L_layer.getFeatures())
        granica = adjaMinus2cmBufor(layer)
        if granica is None or not granica.isValid():
            return []
        granica_geoms = [g.geometry() for g in granica.getFeatures()]
        for feat in buuo_features:
            geom = feat.geometry()
            if geom is None or geom.isEmpty():
                continue
            if geom.length() < 10 and feat['rodzaj'] in ['falochron', 'ostroga']:
                styka_z_granica = any(geom.intersects(g) or geom.crosses(g) for g in granica_geoms)
                if not styka_z_granica:
                    obiektyZbledami.append(feat)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w minDlugoscBUUO: {e}")
        return []


def minDlugoscSUPRnaKUPG(layer):
    try:
        if not layer.isValid():
            return []
        obiektyZbledami = []
        layer_name = layer.name()
        if not layer_name.endswith("SUPR_L"):
            return []
        kupg_name = layer_name.replace("OT_SUPR_L", "OT_KUPG_A")
        supr_layers = QgsProject.instance().mapLayersByName(layer_name)
        kupg_layers = QgsProject.instance().mapLayersByName(kupg_name)
        if not supr_layers or not kupg_layers:
            return []
        SUPR_L_layer = supr_layers[0]
        KUPG_A_layer = kupg_layers[0]
        supr_features = list(SUPR_L_layer.getFeatures())
        kupg_features = list(KUPG_A_layer.getFeatures())
        granica = adjaMinus2cmBufor(layer)
        if granica is None or not granica.isValid():
            return []
        granica_geoms = [g.geometry() for g in granica.getFeatures()]
        kupg_geoms = [f.geometry() for f in kupg_features]
        for feat in supr_features:
            geom = feat.geometry()
            if geom is None or geom.isEmpty():
                continue
            if geom.length() < 100:
                styka_z_granica = any(geom.intersects(g) or geom.crosses(g) for g in granica_geoms)
                if styka_z_granica:
                    continue
                styka_z_kupg = any(geom.intersects(k) or geom.crosses(k) for k in kupg_geoms)
                if not styka_z_kupg:
                    continue
                styka_z_innym_SUPR = any(
                    feat.id() != f.id() and (geom.intersects(f.geometry()) or geom.crosses(f.geometry()))
                    for f in supr_features
                )
                if styka_z_innym_SUPR:
                    continue
                obiektyZbledami.append(feat)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w minDlugoscSUPRnaKUPG: {e}")
        return []


def kontrolaZgodnosciIdentyfikatoraUlicyZNazwa(layer, plikcsv_ulic, plikcsv_simc):
    obiektyZbledami = []
    global loaded_csv_data_ulic, loaded_csv_data_simc
    loaded_csv_data_ulic = loaded_csv_data_ulic if 'loaded_csv_data_ulic' in globals() else None
    loaded_csv_data_simc = loaded_csv_data_simc if 'loaded_csv_data_simc' in globals() else None
    if loaded_csv_data_ulic is None:
        loaded_csv_data_ulic = pd.read_csv(plikcsv_ulic, sep=';', encoding='utf-8', dtype=str)
        loaded_csv_data_ulic = loaded_csv_data_ulic.apply(lambda x: x.str.strip() if x.dtype == "object" else x).fillna('NULL')
        loaded_csv_data_ulic['SYM_UL'] = loaded_csv_data_ulic['SYM_UL'].str.zfill(5)
    if loaded_csv_data_simc is None:
        loaded_csv_data_simc = pd.read_csv(plikcsv_simc, sep=';', encoding='utf-8', dtype=str)
        loaded_csv_data_simc = loaded_csv_data_simc.apply(lambda x: x.str.strip() if x.dtype == "object" else x).fillna('NULL')
        loaded_csv_data_simc['SYM'] = loaded_csv_data_simc['SYM'].str.zfill(7)
    sym_ul_set = set(loaded_csv_data_ulic['SYM_UL'].values)
    sym_set = set(loaded_csv_data_simc['SYM'].values)
    csv_map_ulic = {
        row['SYM_UL']: (row['CECHA'], row['NAZWA_1'], row['NAZWA_2'])
        for _, row in loaded_csv_data_ulic.iterrows()
    }
    for f in layer.getFeatures():
        komunikaty_bledu = []
        identyfikatorULIC = str(f.attribute('identyfikatorULIC')).strip() if 'identyfikatorULIC' in layer.fields().names() and f.attribute('identyfikatorULIC') else None
        identyfikatorSIMC = str(f.attribute('identyfikatorSIMC')).strip() if 'identyfikatorSIMC' in layer.fields().names() and f.attribute('identyfikatorSIMC') else None

        if identyfikatorULIC:
            if len(identyfikatorULIC) != 5:
                komunikaty_bledu.append(f"BŁĘDNA DŁUGOŚĆ identyfikatoraULIC: {identyfikatorULIC}")
            elif identyfikatorULIC not in sym_ul_set:
                komunikaty_bledu.append(f"NIEZNALEZIONY identyfikatorULIC: {identyfikatorULIC}")
        if identyfikatorSIMC:
            if len(identyfikatorSIMC) != 7:
                komunikaty_bledu.append(f"BŁĘDNA DŁUGOŚĆ identyfikatoraSIMC: {identyfikatorSIMC}")
            elif identyfikatorSIMC not in sym_set:
                komunikaty_bledu.append(f"NIEZNALEZIONY identyfikatorSIMC: {identyfikatorSIMC}")
        if komunikaty_bledu:
            gml_id = str(f.attribute("gml_id")) if f.attribute("gml_id") else 'NULL'
            gml_id = (gml_id + " | " if gml_id != 'NULL' else "") + " | ".join(komunikaty_bledu)
            f.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
            layer.updateFeature(f)
            obiektyZbledami.append(f)
            continue
        if not identyfikatorULIC:
            continue
        if 'OT_PTPL_A' in layer.name():
            cecha = str(f.attribute('placCecha')).strip() if f.attribute('placCecha') else 'NULL'
            idx1 = f.fields().indexFromName('placNazwa1')
            nazwa1 = str(f.attribute('placNazwa1')).strip() if idx1 != -1 and f.attribute('placNazwa1') else 'NULL'
            idx = f.fields().indexFromName('placNazwa2')
            nazwa2 = str(f.attribute('placNazwa2')).strip() if idx != -1 and f.attribute('placNazwa2') else 'NULL'
        else:
            cecha = str(f.attribute('ulicaCecha')).strip() if f.attribute('ulicaCecha') else 'NULL'
            nazwa1 = str(f.attribute('ulicaNazwa1')).strip() if f.attribute('ulicaNazwa1') else 'NULL'
            idx2 = f.fields().indexFromName('ulicaNazwa2')
            nazwa2 = str(f.attribute('ulicaNazwa2')).strip() if idx2 != -1 and f.attribute('ulicaNazwa2') else 'NULL'
        cecha_GUS, nazwa1_GUS, nazwa2_GUS = csv_map_ulic.get(identyfikatorULIC, ('NULL', 'NULL', 'NULL'))
        if cecha != cecha_GUS or nazwa1 != nazwa1_GUS or nazwa2 != nazwa2_GUS:
            komunikaty_bledu.append(
                f"NIEZGODNOŚĆ DANYCH: GUS => CECHA:{cecha_GUS}, NAZWA_1:{nazwa1_GUS}, NAZWA_2:{nazwa2_GUS}; "
                f"DANE => CECHA:{cecha}, NAZWA_1:{nazwa1}, NAZWA_2:{nazwa2}"
            )
        if komunikaty_bledu:
            gml_id = str(f.attribute("gml_id")) if f.attribute("gml_id") else 'NULL'
            gml_id = (gml_id + " | " if gml_id != 'NULL' else "") + " | ".join(komunikaty_bledu)
            f.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
            layer.updateFeature(f)
            obiektyZbledami.append(f)

    layer.commitChanges()
    return obiektyZbledami


def pyExpression(layer, sqltxt):
    obiektyZbledami = []
    
    def lexpression(layer, sqltxt):
        expression = QgsExpression(sqltxt)
        
        if expression.hasParserError():
            return f"Parser error: {expression.parserErrorString()}"
        
        if not expression.isValid():
            print("błąd w szablonie kontroli")
        
        # Tworzenie kontekstu wyrażenia
        context = QgsExpressionContext()
        context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(layer))
        
        # Iteracja przez wszystkie obiekty w kontekcie expression
        for feature in layer.getFeatures():
            context.setFeature(feature)
            expression.evaluate(context)
            
            if expression.hasEvalError():
                field_name = expression.evalErrorString().split("'")[1]
                layer.addExpressionField(field_name, QgsField(field_name, QVariant.String))
                
                # iteracyjnie dodaje brakujące pola do warstwy
                lexpression(layer, sqltxt)
                break
    
    lexpression(layer, sqltxt)
    
    request = QgsFeatureRequest(QgsExpression(sqltxt))
    requestFeatures = layer.getFeatures(request)
    for f in requestFeatures:
        obiektyZbledami.append(f)
    
    return obiektyZbledami


def mgrExpression(layer, gml, sqltxt):
    obiektyZbledami = []
    
    if not QgsProject.instance().mapLayersByName(layer.name() + "_dodane_pola"):
        newFields = ['numerPodloza','podloze','szkieletowosc','rodzajGlebyOrganicznej','miazszosc','gatunekMady','gatunekRedziny','informacjeDodatkowe']
        nowePola = []
        
        layer_dodane_pola = QgsVectorLayer('Polygon?crs=EPSG:2180', layer.name() + "_dodane_pola", 'memory')
        memory_provider = layer_dodane_pola.dataProvider()
        memory_provider.addAttributes(layer.fields())
        layer_dodane_pola.updateFields()
        
        for field in newFields:
            for n in [1,2,3,4,5]:
                newFieldName = field + str(n)
                nowePola.append(newFieldName)
                memory_provider.addAttributes([QgsField(newFieldName, QVariant.String)])
        
        for feature in layer.getFeatures():
            memory_provider.addFeature(feature)
        
        layer_dodane_pola.startEditing()
        ns = {'gml': 'http://www.opengis.net/gml/3.2', 'gr': 'urn:gugik:specyfikacje:gmlas:mapaGlebowoRolnicza:1.0'}
        konturyGlebowe = gml.getroot().findall('.//gr:GR_KonturGlebowy', namespaces=ns)
        for konturGlebowy in konturyGlebowe:
            lokalnyId = konturGlebowy.find('.//gr:lokalnyId', namespaces=ns).text
            expression = f"\"lokalnyId\" = '{lokalnyId}'"
            request = QgsFeatureRequest().setFilterExpression(expression)
            lyr_request = layer_dodane_pola.getFeatures(request)
            for feature in lyr_request:
                QCoreApplication.processEvents()
                opisyPodlozy = konturGlebowy.findall('.//gr:opisPodloza', namespaces=ns)
                for opisPodloza in opisyPodlozy:
                    numerPodloza = opisPodloza.find('.//gr:numerPodloza', namespaces=ns).text
                    for field in newFields:
                        value = opisPodloza.find('.//gr:' + field, namespaces=ns)
                        if value == None:
                            wartosc = NULL
                        else:
                            wartosc = value.text
                        feature.setAttribute(layer_dodane_pola.fields().indexFromName(field + numerPodloza), wartosc)
                layer_dodane_pola.updateFeature(feature)
        layer_dodane_pola.commitChanges()
        
        QgsProject.instance().addMapLayer(layer_dodane_pola)
    else:
        layer_dodane_pola = QgsProject.instance().mapLayersByName(layer.name() + "_dodane_pola")[0]
    
    request = QgsFeatureRequest(QgsExpression(sqltxt))
    requestFeatures = layer_dodane_pola.getFeatures(request)
    for f in requestFeatures:
        obiektyZbledami.append(f)
    
    return obiektyZbledami


def numerPodloza(layer, plikGML):
    obiektyZbledami = []
    idenBledow = set()
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'gr': 'urn:gugik:specyfikacje:gmlas:mapaGlebowoRolnicza:1.0'}
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        kontur = featureMember.find('.//gr:GR_KonturGlebowy', namespaces=ns)
        if kontur is not None:
            opisyPodloza =  kontur.findall('.//gr:opisPodloza', namespaces=ns)
            lokalnyId = kontur.find('.//gr:lokalnyId', namespaces=ns)
            lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
            for opisPodloza in opisyPodloza:
                nrPodloza = opisPodloza.find('.//gr:numerPodloza', namespaces=ns)
                nrPodloza_value = nrPodloza.text if nrPodloza is not None else None
                if nrPodloza_value not in ['1','2','3','4','5']:
                    if lokalnyId_text not in idenBledow:
                        expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                        request = QgsFeatureRequest().setFilterExpression(expression)
                        for feature in layer.getFeatures(request):
                                obiektyZbledami.append(feature)
                        idenBledow.add(lokalnyId_text)
    return obiektyZbledami


def miazszoscPodloza(layer, plikGML):
    obiektyZbledami = []
    idenBledow = set()
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'gr': 'urn:gugik:specyfikacje:gmlas:mapaGlebowoRolnicza:1.0'}
    warunki = {
         '-': '2',
         '=': ['2', '3'],
         '.': '3',
         ':': '4',
        ':.': '5',
    }
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        kontur = featureMember.find('.//gr:GR_KonturGlebowy', namespaces=ns)
        if kontur is not None:
            opisyPodloza =  kontur.findall('.//gr:opisPodloza', namespaces=ns)
            lokalnyId = kontur.find('.//gr:lokalnyId', namespaces=ns)
            lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
            for opisPodloza in opisyPodloza:
                nrPodloza = opisPodloza.find('.//gr:numerPodloza', namespaces=ns)
                miazszosc = opisPodloza.find('.//gr:miazszosc', namespaces=ns)
                nrPodloza_value = nrPodloza.text if nrPodloza is not None else None
                miazszosc_value = miazszosc.text if miazszosc is not None else None
                # Iteracja przez warunki w słowniku
                znaleziono_warunek = False
                for miazszosc_key, nrPodloza_values in warunki.items():
                    if miazszosc_value == miazszosc_key:
                        if isinstance(nrPodloza_values, list):
                            for value in nrPodloza_values:
                                if nrPodloza_value != value:
                                    if lokalnyId_text not in idenBledow:
                                        expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                                        request = QgsFeatureRequest().setFilterExpression(expression)
                                        for feature in layer.getFeatures(request):
                                            obiektyZbledami.append(feature)
                                        idenBledow.add(lokalnyId_text)
                        else:
                            if nrPodloza_value != nrPodloza_values:
                                # Jeśli spełnia warunek, dodaj do obiektyZbledami
                                if lokalnyId_text not in idenBledow:
                                    expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                                    request = QgsFeatureRequest().setFilterExpression(expression)
                                    for feature in layer.getFeatures(request):
                                        obiektyZbledami.append(feature)
                                    idenBledow.add(lokalnyId_text)
    
    return obiektyZbledami


def podlozeKompleks(layer, plikGML):
    obiektyZbledami = []
    idenBledow = set()
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'gr': 'urn:gugik:specyfikacje:gmlas:mapaGlebowoRolnicza:1.0'}
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        kontur = featureMember.find('.//gr:GR_KonturGlebowy', namespaces=ns)
        if kontur is not None:
            opisyPodloza =  kontur.findall('.//gr:opisPodloza', namespaces=ns)
            lokalnyId = kontur.find('.//gr:lokalnyId', namespaces=ns)
            lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
            kompleks = kontur.find('.//gr:kompleks', namespaces=ns)
            kompleks_text = kompleks.text if kompleks is not None else "Nieznane ID"
            typPodtyp = kontur.find('.//gr:typPodtyp', namespaces=ns)
            typPodtyp_text = typPodtyp.text if typPodtyp is not None else "Nieznane ID"
            if kompleks_text == 'Tnk':
                for opisPodloza in opisyPodloza:
                    nrPodloza = opisPodloza.find('.//gr:numerPodloza', namespaces=ns)
                    nrPodloza_value = nrPodloza.text if nrPodloza is not None else None
                    podloze = opisPodloza.find('.//gr:podloze', namespaces=ns)
                    podloze_value = podloze.text if podloze is not None else None
                    if typPodtyp_text is not None or podloze_value is not None or nrPodloza_value is not None:
                        if lokalnyId_text not in idenBledow:
                            expression = f"\"lokalnyId\" = '{lokalnyId_text}'"
                            request = QgsFeatureRequest().setFilterExpression(expression)
                            for feature in layer.getFeatures(request):
                                obiektyZbledami.append(feature)
                            idenBledow.add(lokalnyId_text)
                    
    return obiektyZbledami


def kontrola_OT_ADJA_A_z_A02_Granice_powiatow(layer):
    obiektyZbledami = []
    extractbyexpression = processing.run("native:extractbyexpression", {
        'INPUT': layer,
        'EXPRESSION': '"rodzaj" IN (\'państwo\', \'województwo\', \'powiat\')',
        'OUTPUT': 'memory:'
    })['OUTPUT']   
    terytyPowiatow = [str(obj['identyfikatorTERYTjednostki']) for obj in extractbyexpression.getFeatures()]
    config = configparser.ConfigParser()
    mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath()) / "python/plugins/Walidator_plikow_gml/"
    config.read(str(mainPath) + '/Walidator_plikow_gml.ini')
    granicePowiatowPath = config['DEFAULT']['granicepowiatow']
    granicePowiatow = QgsVectorLayer(granicePowiatowPath, 'GranicePowiatow', 'ogr')
    granicePowiatow_z_PRG = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "granica powiatu z PRG", "memory")    
    granicePowiatow = processing.run("native:multiparttosingleparts", {
        'INPUT': granicePowiatow,
        'OUTPUT': 'memory:'
    })['OUTPUT']    
    for obj in granicePowiatow.getFeatures():
        if str(obj['JPT_KOD_JE']) in terytyPowiatow:
            granicePowiatow_z_PRG.dataProvider().addFeatures([obj])
    
    extractbyexpression_linia = processing.run("native:polygonstolines", {
        'INPUT': extractbyexpression,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    granicePowiatow_z_PRG_linia = processing.run("native:polygonstolines", {
        'INPUT': granicePowiatow_z_PRG,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    bufor = processing.run("native:buffer", {
        'INPUT': granicePowiatow_z_PRG_linia,
        'DISTANCE': 0.02,
        'SEGMENTS': 10,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    difference = processing.run("native:difference", {
        'INPUT': extractbyexpression_linia,
        'OVERLAY': bufor,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    pojedynczeObiekty = processing.run("native:multiparttosingleparts", {
        'INPUT': difference,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    if pojedynczeObiekty.featureCount() > 0:
        if not any(lyr.name() == "granica powiatu z PRG" for lyr in QgsProject.instance().mapLayers().values()):
            QgsProject.instance().addMapLayer(granicePowiatow_z_PRG)
        obiektyZbledami.extend(pojedynczeObiekty.getFeatures())
    
    return obiektyZbledami


def kontrola_OT_ADJA_A_z_A03_Granice_gmin(layer):
    obiektyZbledami = []
    extractbyexpression = processing.run("native:extractbyexpression", {
        'INPUT': layer,
        'EXPRESSION': '"rodzaj" = \'gmina\'',
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    terytyGmin = [str(f['identyfikatorTERYTjednostki']) for f in extractbyexpression.getFeatures()]
    
    config = configparser.ConfigParser()
    mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath()) / "python/plugins/Walidator_plikow_gml/"
    config.read(str(mainPath) + '/Walidator_plikow_gml.ini')
    graniceGminPath = config['DEFAULT']['granicegmin']
    graniceGmin = QgsVectorLayer(graniceGminPath, 'GraniceGmin', 'ogr')
    graniceGmin_z_PRG = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "granice gmin z PRG", "memory")
    
    graniceGmin = processing.run("native:multiparttosingleparts", {
        'INPUT': graniceGmin,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    for obj in graniceGmin.getFeatures():
        if str(obj['JPT_KOD_JE']) in terytyGmin:
            graniceGmin_z_PRG.dataProvider().addFeatures([obj])
    
    extractbyexpression_linia = processing.run("native:polygonstolines", {
        'INPUT': extractbyexpression,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    graniceGmin_z_PRG_linia = processing.run("native:polygonstolines", {
        'INPUT': graniceGmin_z_PRG,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    bufor = processing.run("native:buffer", {
        'INPUT': graniceGmin_z_PRG_linia,
        'DISTANCE': 0.02,
        'SEGMENTS': 10,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    difference = processing.run("native:difference", {
        'INPUT': extractbyexpression_linia,
        'OVERLAY': bufor,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    pojedynczeObiekty = processing.run("native:multiparttosingleparts", {
        'INPUT': difference,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    if pojedynczeObiekty.featureCount() > 0:
        if "granice gmin z PRG" not in [lyr.name() for lyr in QgsProject.instance().mapLayers().values()]:
            QgsProject.instance().addMapLayer(graniceGmin_z_PRG)
        obiektyZbledami.extend(pojedynczeObiekty.getFeatures())
    
    return obiektyZbledami



def kontrola_OT_ADJA_A_z_A05_Granice_jednostek_ewidencyjnych(layer):
    obiektyZbledami = []
    extractbyexpression = processing.run("native:extractbyexpression", {
        'INPUT': layer,
        'EXPRESSION': '"rodzaj" = \'miasto w gminie miejsko-wiejskiej\'',
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    terytyMiast, teryt = [], ''
    for f in extractbyexpression.getFeatures():
        if f['rodzaj'] == 'miasto w gminie miejsko-wiejskiej':
            Teryt = str(f['identyfikatorTERYTjednostki']).zfill(7)
            terytyMiast.append(Teryt)
            teryt = Teryt[:4]
    
    config = configparser.ConfigParser()
    mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath()) / "python/plugins/Walidator_plikow_gml/"
    config.read(str(mainPath) + '/Walidator_plikow_gml.ini')
    granicePath = config['DEFAULT']['granicejednostekewidencyjnych']
    graniceJE = QgsVectorLayer(granicePath, 'GraniceJednostekEwidencyjnych', 'ogr')
    graniceJE_z_PRG = QgsVectorLayer("Polygon?crs=epsg:2180&field=gml_id:string(254)", "granice jednostek ewidencyjnych z PRG", "memory")
    
    graniceJE = processing.run("native:multiparttosingleparts", {
        'INPUT': graniceJE,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    for f in graniceJE.getFeatures():
        JPT = str(f['JPT_KOD_JE']).replace("_", "")
        if JPT in terytyMiast:
            graniceJE_z_PRG.dataProvider().addFeatures([f])
        elif JPT[:4] == teryt and JPT[-1] in ['3','4']:
            newF = QgsFeature(graniceJE_z_PRG.fields())
            newF.setAttribute(0, 'nie dotyczy')
            newF.setGeometry(f.geometry())
            obiektyZbledami.append(newF)
    
    extract_linia = processing.run("native:polygonstolines", {
        'INPUT': extractbyexpression,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    je_linia = processing.run("native:polygonstolines", {
        'INPUT': graniceJE_z_PRG,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    bufor = processing.run("native:buffer", {
        'INPUT': je_linia,
        'DISTANCE': 0.02,
        'SEGMENTS': 10,
        'DISSOLVE': True,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    difference = processing.run("native:difference", {
        'INPUT': extract_linia,
        'OVERLAY': bufor,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    pojedyncze = processing.run("native:multiparttosingleparts", {
        'INPUT': difference,
        'OUTPUT': 'memory:'
    })['OUTPUT']
    
    if pojedyncze.featureCount() > 0:
        if "granice jednostek ewidencyjnych z PRG" not in [lyr.name() for lyr in QgsProject.instance().mapLayers().values()]:
            QgsProject.instance().addMapLayer(graniceJE_z_PRG)
        obiektyZbledami.extend(pojedyncze.getFeatures())
    
    return obiektyZbledami


def kompletnoscPlikowBDOT10k(pliki):
    obiektyZbledami = []
    
    listaKlas = ['OT_ADJA_A','OT_ADMS_A','OT_ADMS_P','OT_BUBD_A','OT_BUHD_A','OT_BUHD_L','OT_BUIB_A','OT_BUIB_L','OT_BUIN_L','OT_BUIT_A',
                 'OT_BUIT_P','OT_BUSP_A','OT_BUSP_L','OT_BUTR_L','OT_BUTR_P','OT_BUUO_L','OT_BUWT_A','OT_BUWT_P','OT_BUZM_L','OT_BUZT_A',
                 'OT_BUZT_P','OT_KUHO_A','OT_KUHU_A','OT_KUKO_A','OT_KUKO_P','OT_KUMN_A','OT_KUOS_A','OT_KUOZ_A','OT_KUPG_A','OT_KUPG_P',
                 'OT_KUPW_A','OT_KUSC_A','OT_KUSK_A','OT_KUZA_A','OT_OIKM_A','OT_OIKM_L','OT_OIKM_P','OT_OIMK_A','OT_OIOR_A','OT_OIOR_L',
                 'OT_OIOR_P','OT_OIPR_L','OT_OIPR_P','OT_OISZ_A','OT_PTGN_A','OT_PTKM_A','OT_PTLZ_A','OT_PTNZ_A','OT_PTPL_A','OT_PTRK_A',
                 'OT_PTSO_A','OT_PTTR_A','OT_PTUT_A','OT_PTWP_A','OT_PTWZ_A','OT_PTZB_A','OT_RTLW_L','OT_RTPW_P','OT_SKDR_L','OT_SKJZ_L',
                 'OT_SKPP_L','OT_SKRP_L','OT_SKRW_P','OT_SKTR_L','OT_SULN_L','OT_SUPR_L','OT_SWKN_L','OT_SWRM_L','OT_SWRS_L','OT_TCON_A',
                 'OT_TCPK_A','OT_TCPN_A','OT_TCRZ_A']
    
    brakujaceKlasy = QgsVectorLayer("Point?crs=epsg:2180&field=gml_id:string(254)", "brakujace klasy", "memory")
    
    for klasa in listaKlas:
        brakKlasy = True
        for plik in pliki:
            if plik.__contains__(klasa):
                brakKlasy = False
                break
        if brakKlasy:
            brakujaceKlasy.startEditing()
            nowyRekord = QgsFeature(brakujaceKlasy.fields())
            nowyRekord.setAttribute(0, klasa)
            nowyRekord.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(0, 0)))
            brakujaceKlasy.addFeature(nowyRekord)
            brakujaceKlasy.commitChanges()
            obiektyZbledami.append(nowyRekord)
    
    return obiektyZbledami


def kompletnoscObiektowBDOT10k(layer, plikGMLzrodlowy, plikGML):
    obiektyZbledami = []
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'
    }
    rootKontrolowane = lxml.etree.parse(plikGML).getroot()
    lokalnyIdsKontrolowane = [
        el.text for el in rootKontrolowane.findall('.//ot:lokalnyId', namespaces=ns)
    ]
    try:
        rootZrodla = lxml.etree.parse(plikGMLzrodlowy).getroot()
        lokalnyIdsZrodla = [
            el.text for el in rootZrodla.findall('.//ot:lokalnyId', namespaces=ns)
        ]
    except Exception:
        return obiektyZbledami
        
    if not lokalnyIdsZrodla:
        return obiektyZbledami
    brakujace = set(lokalnyIdsZrodla) - set(lokalnyIdsKontrolowane)
    if brakujace:
        layerZrodlo = QgsVectorLayer(plikGMLzrodlowy, 'BDOT10k_dane_zrodlowe', 'ogr')
        if not layerZrodlo.isValid():
            return obiektyZbledami
        for lokalnyId in brakujace:
            expr = QgsExpression(f'"lokalnyId" = \'{lokalnyId}\'')
            request = QgsFeatureRequest(expr)
            for feat in layerZrodlo.getFeatures(request):
                obiektyZbledami.append(feat)
    return obiektyZbledami


def kontrolaZmianAtrybutowWzgledemWersji(layer, plikGMLzrodlowy, plikGML):
    """
    Kontrola zmian atrybutów i geometrii obiektów względem wersji.
    Wykrywa nieuzasadnione lub pominięte aktualizacje wersji i oznaczenia zmiany.
    """    
    obiektyZbledami=set()
    kolejnosc_only_count_by_layer = defaultdict(int)
    precyzja_only_count_by_layer = defaultdict(int)
    precyzja_geom_count_by_layer = defaultdict(int)
    naprawa_bledu_zrodlowego_counter = defaultdict(int)
    parser=etree.XMLParser(remove_blank_text=True)
    try:
        k_root=etree.parse(plikGML, parser).getroot()
        z_root=etree.parse(plikGMLzrodlowy, parser).getroot()
    except Exception:
        return obiektyZbledami
    ns={'gml':'http://www.opengis.net/gml/3.2',
        'ot':'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'}
    for root in (k_root,z_root):
        for elem in root.xpath('//*[@gml:id]',namespaces=ns):
            if '{http://www.opengis.net/gml/3.2}id' in elem.attrib: del elem.attrib['{http://www.opengis.net/gml/3.2}id']
        for elem in root.xpath('//gml:posList|//gml:pos',namespaces=ns):
            if elem.text: elem.text=elem.text.strip()
    fMembers_K_dic={f.find('.//ot:lokalnyId',ns).text:f for f in k_root.findall('.//gml:featureMember',ns) if f.find('.//ot:lokalnyId',ns) is not None and f.find('.//ot:lokalnyId',ns).text}
    fMembers_Z_dic={f.find('.//ot:lokalnyId',ns).text:f for f in z_root.findall('.//gml:featureMember',ns) if f.find('.//ot:lokalnyId',ns) is not None and f.find('.//ot:lokalnyId',ns).text}
    badfMember_Z_dic={}
    def analiza_geometrii(elem_k,elem_z):
        zmiany=set()
        coords_k,coords_z=[],[]
        coords_str_k,coords_str_z=[],[]
        geom_type=None
        for tag in ['posList','pos']:
            for e in elem_k.findall('.//gml:'+tag,ns):
                if e.text:
                    coords_str_k.extend(e.text.strip().split())
                    coords_k.extend([float(c) for c in e.text.strip().split()])
                    geom_type='Polygon' if tag=='posList' and e.getparent().tag.endswith('LinearRing') else 'LineString' if tag=='posList' else 'Point'
            for e in elem_z.findall('.//gml:'+tag,ns):
                if e.text:
                    coords_str_z.extend(e.text.strip().split())
                    coords_z.extend([float(c) for c in e.text.strip().split()])
        if coords_k!=coords_z:
            zmiany.add('geometria')
            if not coords_k or not coords_z: return zmiany
            if len(coords_k)!=len(coords_z): zmiany.add('zmiana liczby wierzchołków'); return zmiany
            precyzja_flag=False
            for c_s_k,c_s_z in zip(coords_str_k,coords_str_z):
                mp=lambda s: len(s.split('.')[1]) if '.' in s else 0
                if mp(c_s_k)!=mp(c_s_z): precyzja_flag=True; break
            if precyzja_flag: zmiany.add('różnica w precyzji zapisu współrzędnych')
            if geom_type=='Polygon':
                def pole_ring(c): pts=[(c[i],c[i+1]) for i in range(0,len(c)-1,2) if i+1<len(c)]; return abs(sum(pts[i][0]*pts[(i+1)%len(pts)][1]-pts[(i+1)%len(pts)][0]*pts[i][1] for i in range(len(pts)))/2.0) if len(pts)>=3 else 0.0
                def split_rings(elem):
                    rings=[] 
                    for lr in elem.findall('.//gml:LinearRing',ns):
                        for pos in lr.findall('.//gml:posList',ns):
                            if pos.text: coords=[float(v) for v in pos.text.strip().split()]; 
                            if len(coords)>=6: rings.append(coords)
                    return rings
                def pole_polygon(rings): return pole_ring(rings[0])-sum(pole_ring(r) for r in rings[1:]) if rings else 0.0
                def rotacja_po_punktach(r): return [(r[i],r[i+1]) for i in range(0,len(r)-1,2) if i+1<len(r)]
                rings_k,rings_z=split_rings(elem_k),split_rings(elem_z)
                if not rings_k or not rings_z: return zmiany
                if len(rings_k)!=len(rings_z): zmiany.add('zmiana położenia')
                for r_k,r_z in zip(rings_k,rings_z):
                    pts_k,pts_z=rotacja_po_punktach(r_k),rotacja_po_punktach(r_z)
                    n_pts=len(pts_k); rot_ok=False
                    for shift in range(n_pts):
                        rotated=pts_k[shift:]+pts_k[:shift]
                        if rotated==pts_z or rotated[::-1]==pts_z: zmiany.add('różnica w kolejności wierzchołków'); rot_ok=True; break
                    if not rot_ok and set((round(x,6),round(y,6)) for x,y in pts_k)==set((round(x,6),round(y,6)) for x,y in pts_z): zmiany.add('różnica w kolejności wierzchołków'); rot_ok=True
                    if not rot_ok: zmiany.add('zmiana położenia')
                if abs(pole_polygon(rings_k)-pole_polygon(rings_z))>1e-6: zmiany.add('zmiana położenia')
            elif geom_type=='LineString':
                przesuniecie=any(abs(a-b)>1e-6 for a,b in zip(coords_k,coords_z))
                dlugosc=lambda c: sum(((c[i+1][0]-c[i][0])**2+(c[i+1][1]-c[i][1])**2)**0.5 for i in range(len(c)-1))
                coords_k_tuples=[tuple(coords_k[i:i+2]) for i in range(0,len(coords_k),2)]
                coords_z_tuples=[tuple(coords_z[i:i+2]) for i in range(0,len(coords_z),2)]
                if coords_k_tuples[::-1]==coords_z_tuples: zmiany.add('różnica w kolejności wierzchołków')
                elif przesuniecie or abs(dlugosc(coords_k_tuples)-dlugosc(coords_z_tuples))>1e-6: zmiany.add('zmiana położenia')
            elif geom_type=='Point':
                if any(abs(a-b)>1e-6 for a,b in zip(coords_k,coords_z)): zmiany.add('zmiana położenia')
        return zmiany
    for lokalnyId in fMembers_Z_dic:
        if lokalnyId in fMembers_K_dic:
            k_fm=fMembers_K_dic[lokalnyId]; z_fm=fMembers_Z_dic[lokalnyId]                      
            wersja_k=k_fm.find('.//ot:wersja',ns); wersja_z=z_fm.find('.//ot:wersja',ns); ozn_zm_k = k_fm.find('.//ot:oznaczenieZmiany',ns); ozn_zm_z = z_fm.find('.//ot:oznaczenieZmiany',ns)
            # --- NIEUZASADNIONA ZMIANA WERSJI / BRAK AKTUALIZACJI oznaczenieZmiany
            geom_details = analiza_geometrii(k_fm, z_fm)
            geom_exceptions = {'różnica w kolejności wierzchołków', 'różnica w precyzji zapisu współrzędnych'}          
            geom_istotne = [g for g in geom_details if g not in geom_exceptions]
            zmiany_geometrii = bool(geom_istotne)
            wykluczone = {'lokalnyId', 'przestrzenNazw', 'wersja', 'oznaczenieZmiany', 'poczatekWersjiObiektu'}
            def atts_dict(fm):
                wynik = defaultdict(list)
                for e in fm.iter():
                    tag = e.tag.split("}")[-1]
                    if tag not in wykluczone and e.text is not None:
                        wynik[tag].append(e.text)
                return dict(wynik)
            att_k, att_z = atts_dict(k_fm), atts_dict(z_fm)
            zmiany_atrybutow = att_k != att_z
            zmiany_danych = zmiany_atrybutow or zmiany_geometrii  # TYLKO istotne
            wersja_sie_zmienila = (
                wersja_k is not None and wersja_z is not None
                and (wersja_k.text or "") != (wersja_z.text or "")
            )
            ozn_zm_sie_zmienilo = (
                ((ozn_zm_k.text or "") if ozn_zm_k is not None else "") !=
                ((ozn_zm_z.text or "") if ozn_zm_z is not None else "")
            )
            # --- MODUŁ: BŁĄD W DANYCH ŹRÓDŁOWYCH
            wersja_z_txt = (wersja_z.text or "") if wersja_z is not None else ""
            poczatek_z = z_fm.find('.//ot:poczatekWersjiObiektu', ns)
            poczatek_z_txt = (poczatek_z.text or "") if poczatek_z is not None else ""
            wersja_k_txt = (wersja_k.text or "") if wersja_k is not None else ""
            poczatek_k = k_fm.find('.//ot:poczatekWersjiObiektu', ns)
            poczatek_k_txt = (poczatek_k.text or "") if poczatek_k is not None else ""
            blad_zrodlowy = (
                wersja_z_txt
                and poczatek_z_txt
                and wersja_z_txt != poczatek_z_txt
            )
            ozn_zm_k_txt = (ozn_zm_k.text or "") if ozn_zm_k is not None else ""
            ozn_zm_z_txt = (ozn_zm_z.text or "") if ozn_zm_z is not None else ""
            blad_zrodlowy_naprawiony = (
                blad_zrodlowy
                and wersja_k_txt == poczatek_k_txt
                and wersja_k_txt != wersja_z_txt
            )
            if blad_zrodlowy_naprawiony:
                if not ozn_zm_sie_zmienilo:
                    badfMember_Z_dic.setdefault(lokalnyId, set()).add(
                        'zmiana wersji bez aktualizacji atrybutu oznaczenieZmiany - BŁĄD'
                    )
                pomijaj_nieuzasadniona_zmiane_wersji = True
            else:
                pomijaj_nieuzasadniona_zmiane_wersji = False

            if wersja_sie_zmienila and not zmiany_danych and not pomijaj_nieuzasadniona_zmiane_wersji:
                badfMember_Z_dic.setdefault(lokalnyId, set()).add(
                    'nieuzasadniona zmiana wersji - BŁĄD'
                )
            elif wersja_sie_zmienila and zmiany_danych and not ozn_zm_sie_zmienilo:
                badfMember_Z_dic.setdefault(lokalnyId, set()).add(
                    'zmiana wersji bez aktualizacji atrybutu oznaczenieZmiany - BŁĄD'
                )
            elif not wersja_sie_zmienila and ozn_zm_sie_zmienilo and not zmiany_danych:
                badfMember_Z_dic.setdefault(lokalnyId, set()).add(
                    'nieuzasadniona zmiana atrybutu oznaczenieZmiany - BŁĄD'
                )
            # ZMIANA ATRYBUTÓW BEZ ZMIANY WERSJI    
            elif wersja_k is not None and wersja_z is not None and wersja_k.text == wersja_z.text:
                if etree.tostring(k_fm, method="c14n", exclusive=True) != etree.tostring(z_fm, method="c14n", exclusive=True):
                    wymiarowe = ['szerokosc', 'szerokoscNawierzchni', 'wysokoscZapory',
                                 'wysokosc', 'szerokoscPodstawy', 'szerokoscKorony', 'nosnosc']
                    def wartosci_rownowazne(val1,val2,is_wymiarowy=False):
                        if is_wymiarowy:
                            try: return float(val1)==float(val2)
                            except: return val1==val2
                        return val1==val2
                    atrybutyZRoznica,wymiarowe_precyzja=set(),set()
                    geom_details=analiza_geometrii(k_fm,z_fm)
                    for elem_z in z_fm.iter():
                        tagname=elem_z.tag.split("}")[-1]
                        if tagname in ['featureMember'] or tagname.startswith('OT_') or tagname in ['posList','LinearRing','LineString','exterior','interior','Polygon','segments','LineStringSegment','Curve','pos','Point']: continue
                        is_wymiarowy=tagname in wymiarowe
                        znaleziono=any(wartosci_rownowazne(elem_k.text,elem_z.text,is_wymiarowy) for elem_k in k_fm.findall('.//'+elem_z.tag))
                        if not znaleziono: atrybutyZRoznica.add(tagname)
                        elif is_wymiarowy and k_fm.find('.//ot:'+tagname,ns) is not None and z_fm.find('.//ot:'+tagname,ns) is not None and elem_z.text!=k_fm.find('.//ot:'+tagname,ns).text: wymiarowe_precyzja.add(tagname)
                    for elem_k in k_fm.iter():
                        tagname=elem_k.tag.split("}")[-1]
                        if tagname in ['featureMember'] or tagname.startswith('OT_') or tagname in ['posList','LinearRing','LineString','exterior','interior','Polygon','segments','LineStringSegment','Curve','pos','Point']: continue
                        is_wymiarowy=tagname in wymiarowe
                        znaleziono=any(wartosci_rownowazne(elem_z.text,elem_k.text,is_wymiarowy) for elem_z in z_fm.findall('.//'+elem_k.tag))
                        if not znaleziono: atrybutyZRoznica.add(tagname)
                        elif is_wymiarowy and k_fm.find('.//ot:'+tagname,ns) is not None and z_fm.find('.//ot:'+tagname,ns) is not None and elem_k.text!=z_fm.find('.//ot:'+tagname,ns).text: wymiarowe_precyzja.add(tagname)
                    if atrybutyZRoznica or geom_details or wymiarowe_precyzja:
                        geom_exceptions = {'różnica w kolejności wierzchołków', 'różnica w precyzji zapisu współrzędnych'}
                        geom_norm = [s.strip().lower() for s in geom_details]
                        real_geom_change = any(s not in {e.lower() for e in geom_exceptions} and s.lower() != 'geometria' for s in geom_norm)
                        layer_suffix = layer.name()[-9:]
                        if any('różnica w kolejności wierzchołków' in s for s in geom_norm):
                            kolejnosc_only_count_by_layer[layer_suffix] += 1
                        if any('różnica w precyzji zapisu współrzędnych' in s for s in geom_norm):
                            precyzja_geom_count_by_layer[layer_suffix] += 1
                        if wymiarowe_precyzja:
                            precyzja_only_count_by_layer[layer_suffix] += len(wymiarowe_precyzja)
                        if not atrybutyZRoznica and not real_geom_change and (
                            any('różnica w kolejności wierzchołków' in s for s in geom_norm)
                            or any('różnica w precyzji zapisu współrzędnych' in s for s in geom_norm)
                            or wymiarowe_precyzja
                        ):
                            continue
                        dopisek = ' - BŁĄD' if atrybutyZRoznica or real_geom_change else ''
                        geom_main = ['geometria'] if real_geom_change else []
                        geom_main += [
                            s for s in geom_details
                            if s.strip().lower() not in {e.lower() for e in geom_exceptions}
                            and s.strip().lower() != 'geometria'
                        ]
                        geom_ex_only = [
                            s for s in geom_details
                            if s.strip().lower() in {e.lower() for e in geom_exceptions}
                        ]
                        komentarz_parts = sorted(atrybutyZRoznica) + geom_main + sorted(geom_ex_only)
                        nieistotne = {'różnica w kolejności wierzchołków', 'różnica w precyzji zapisu współrzędnych'}
                        komentarz_parts_istotne = [k for k in komentarz_parts if k not in {"różnica w kolejności wierzchołków","różnica w precyzji zapisu współrzędnych"}]
                        if not komentarz_parts_istotne:
                            continue
                        if komentarz_parts_istotne:
                            badfMember_Z_dic[lokalnyId] = {
                                'obiekt ma tę samą wersję i zmienione atrybuty: ' + '; '.join(komentarz_parts_istotne) + dopisek
                            }    
    if badfMember_Z_dic:
        fields=layer.fields(); idx_gml_id=fields.indexFromName("gml_id")
        layer.startEditing()
        for feature in layer.getFeatures():
            lokalnyId=feature['lokalnyId']
            if lokalnyId in badfMember_Z_dic:
                feature.setAttribute(idx_gml_id,f"{feature['gml_id']}|{'|'.join(badfMember_Z_dic[lokalnyId])}")
                layer.updateFeature(feature)
                obiektyZbledami.add(feature)
        layer.commitChanges()
    for layer_name, count in precyzja_only_count_by_layer.items():
        print(f"Łącznie {count} obiektów {layer_name} różni się precyzją zapisu wartości wymiarowych atrybutów")
    for layer_name, count in kolejnosc_only_count_by_layer.items():
        print(f"Łącznie {count} obiektów {layer_name} różni się kolejnością wierzchołków")
    for layer_name, count in precyzja_geom_count_by_layer.items():
        print(f"Łącznie {count} obiektów {layer_name} różni się precyzją zapisu współrzędnych (geometria)")
    for layer_name, count in naprawa_bledu_zrodlowego_counter.items():
        print(f"Łącznie dla {count} obiektów {layer_name} naprawiono błąd danych źródłowych: wersja ≠ poczatekWersjiObiektu")   
    del fMembers_Z_dic,fMembers_K_dic,badfMember_Z_dic
    return obiektyZbledami


global oznaczeniaWRamachKlas
oznaczeniaWRamachKlas = {}
def kontrolaOznaczeniaZmiany_Global(layer, plikGMLzrodlowy, plikGML):
    obiektyZbledami = set()
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'
    }
    parser = etree.XMLParser(remove_blank_text=True)
    try:
        k_root = etree.parse(plikGML, parser).getroot()
        z_root = etree.parse(plikGMLzrodlowy, parser).getroot()
    except Exception:
        return obiektyZbledami
    fMembers_K_dic = {f.find('.//ot:lokalnyId', ns).text: f
                      for f in k_root.findall('.//gml:featureMember', ns)
                      if f.find('.//ot:lokalnyId', ns) is not None and f.find('.//ot:lokalnyId', ns).text}
    fMembers_Z_dic = {f.find('.//ot:lokalnyId', ns).text: f
                      for f in z_root.findall('.//gml:featureMember', ns)
                      if f.find('.//ot:lokalnyId', ns) is not None and f.find('.//ot:lokalnyId', ns).text}
    temp_oznaczenia = []
    badfMember_Z_dic = {}

    # --- dominanta i niespójności (tylko na nowych lub zmienionych obiektach)
    for lokalnyId, k_fm in fMembers_K_dic.items():
        z_fm = fMembers_Z_dic.get(lokalnyId)
        wersja_k = k_fm.find('.//ot:wersja', ns)
        wersja_z = z_fm.find('.//ot:wersja', ns) if z_fm else None
        is_new = not z_fm
        is_changed = False
        if wersja_k is not None and wersja_z is not None:
            try:
                is_changed = int(wersja_k.text) > int(wersja_z.text)
            except ValueError:
                is_changed = wersja_k.text != wersja_z.text
        if is_new or is_changed:
            ozn = k_fm.find('.//ot:oznaczenieZmiany', ns)
            val = ozn.text.strip() if (ozn is not None and ozn.text) else None
            if val:
                temp_oznaczenia.append((lokalnyId, val))
    global oznaczeniaWRamachKlas
    for lokalnyId, val in temp_oznaczenia:
        oznaczeniaWRamachKlas.setdefault(lokalnyId, []).append(val)
    if not oznaczeniaWRamachKlas:
        return obiektyZbledami
    licznik_globalny = Counter(v for vals in oznaczeniaWRamachKlas.values() for v in vals)
    dominanta, _ = licznik_globalny.most_common(1)[0]
    for lokalnyId, val in temp_oznaczenia:
        if val != dominanta:
            badfMember_Z_dic.setdefault(lokalnyId, set()).add(
                f"niespójność – dominująca wartość '{dominanta}', obiekt ma '{val} - DO INTERPRETACJI'"
            )
    if badfMember_Z_dic:
        idx_gml_id = layer.fields().indexFromName("gml_id")
        layer.startEditing()
        for feature in layer.getFeatures():
            lokalnyId = feature['lokalnyId']
            if lokalnyId in badfMember_Z_dic:
                feature.setAttribute(idx_gml_id, f"{feature['gml_id']}|{'|'.join(badfMember_Z_dic[lokalnyId])}")
                layer.updateFeature(feature)
                obiektyZbledami.add(feature)
        layer.commitChanges()

    return obiektyZbledami



def kontrolaDatyWersji(layer, plikGMLzrodlowy, plikGML):
    """
    Kontrola daty wersji obiektów BDOT10k.
    Sprawdza, czy nowe i zmienione obiekty nie mają daty starszej
    niż najświeższa data w pliku źródłowym danej warstwy.
    """
    obiektyZbledami = set()
    parser = etree.XMLParser(remove_blank_text=True)
    try:
        k_root = etree.parse(plikGML, parser).getroot()
        z_root = etree.parse(plikGMLzrodlowy, parser).getroot()
    except Exception:
        return obiektyZbledami
    ns = {'gml': 'http://www.opengis.net/gml/3.2',
          'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'}
    fMembers_K_dic = {f.find('.//ot:lokalnyId', ns).text: f
                      for f in k_root.findall('.//gml:featureMember', ns)
                      if f.find('.//ot:lokalnyId', ns) is not None and f.find('.//ot:lokalnyId', ns).text}
    fMembers_Z_dic = {f.find('.//ot:lokalnyId', ns).text: f
                      for f in z_root.findall('.//gml:featureMember', ns)
                      if f.find('.//ot:lokalnyId', ns) is not None and f.find('.//ot:lokalnyId', ns).text}

    feature_dic = {f['lokalnyId']: f for f in layer.getFeatures()}
    # Najświeższa data wersji w pliku źródłowym dla tej warstwy
    data_wersji_layer = []
    for z_fm in fMembers_Z_dic.values():
        wersja_z = z_fm.find('.//ot:wersja', ns)
        if wersja_z is not None and wersja_z.text:
            try:
                data_z_dt = datetime.strptime(wersja_z.text.strip().split('T')[0], "%Y-%m-%d")
                data_wersji_layer.append(int(data_z_dt.strftime('%Y%m%d')))
            except Exception:
                continue
    max_data_aktualizacji_layer = max(data_wersji_layer) if data_wersji_layer else None
    badfMember_dic = {}
    for lokalnyId, k_fm in fMembers_K_dic.items():
        if lokalnyId not in fMembers_Z_dic:
            is_new_or_changed = True
        else:
            wersja_z = fMembers_Z_dic[lokalnyId].find('.//ot:wersja', ns)
            wersja_k = k_fm.find('.//ot:wersja', ns)
            if wersja_z is not None and wersja_k is not None:
                is_new_or_changed = wersja_k.text != wersja_z.text
            else:
                is_new_or_changed = False
        if is_new_or_changed and k_fm.find('.//ot:wersja', ns) is not None:
            try:
                data_k_dt = datetime.strptime(k_fm.find('.//ot:wersja', ns).text.strip().split('T')[0], "%Y-%m-%d")
                data_k_int = int(data_k_dt.strftime('%Y%m%d'))
                if max_data_aktualizacji_layer is not None and data_k_int < max_data_aktualizacji_layer:
                    badfMember_dic.setdefault(lokalnyId, set()).add(
                        f"nowy lub zmieniony obiekt ma datę wersji ({data_k_int}) starszą niż aktualność pliku źródłowego - BŁĄD"
                    )
            except Exception:
                continue
    if badfMember_dic:
        idx_gml_id = layer.fields().indexFromName("gml_id")
        layer.startEditing()
        for lokalnyId, messages in badfMember_dic.items():
            feature = feature_dic.get(lokalnyId)
            if feature:
                feature.setAttribute(idx_gml_id, f"{feature['gml_id']}|{'|'.join(messages)}")
                layer.updateFeature(feature)
                obiektyZbledami.add(feature)
        layer.commitChanges()

    return obiektyZbledami
    


vl_cykl_zycia_global = None
def kontrolaCykluZycia(layer, plikGML):
    """
    Kontrola cyklu życia obiektów BDOT10k Wykrywa niespójność dat:
    1) koniecWersjiObiektu < poczatekWersjiObiektu
    2) wersja != koniecWersjiObiektu
    """
    obiektyZbledami=[]
    if not plikGML or not layer: return obiektyZbledami
    def safe_parse_date(s):
        try: return datetime.strptime(s.split('T')[0], "%Y-%m-%d") if s else None
        except: return None
    try: root = plikGML.getroot()
    except: return obiektyZbledami
    schema_fields = QgsFields(); [schema_fields.append(QgsField(f,QVariant.String)) for f in ["klasa","gml_id","komunikat"]]
    class_name = layer.name()[-9:] if layer and layer.name() else "brak_klasy"
    bledy_gml_dic={}
    for fm in root.findall(".//{http://www.opengis.net/gml/3.2}featureMember"):
        feature_elem = next(iter(fm), None)
        if feature_elem is None: continue
        gml_id = feature_elem.attrib.get('{http://www.opengis.net/gml/3.2}id',"Brak")
        poczatek_dt = koniec_dt = None
        for child in feature_elem.iter():
            tag=child.tag.split('}')[-1].lower()
            if tag=='poczatekwersjiobiektu': poczatek_dt=safe_parse_date(child.text)
            elif tag=='koniecwersjiobiektu': koniec_dt=safe_parse_date(child.text)
        if poczatek_dt and koniec_dt and koniec_dt<poczatek_dt:
            bledy_gml_dic.setdefault(gml_id,[]).append(f"koniec wersji ({koniec_dt}) wcześniejszy niż początek ({poczatek_dt})")
        wersja_elem = feature_elem.find('.//{*}wersja')
        koniec_elem = feature_elem.find('.//{*}koniecWersjiObiektu')
        wersja_txt = wersja_elem.text if wersja_elem is not None else None
        koniec_txt = koniec_elem.text if koniec_elem is not None else None
        if wersja_txt and koniec_txt and wersja_txt!=koniec_txt:
            bledy_gml_dic.setdefault(gml_id,[]).append(f"'wersja' ({wersja_txt}) ≠ 'koniecWersjiObiektu' ({koniec_txt})")
    nowe_bledy=[]
    for gml_id, komunikaty in bledy_gml_dic.items():
        tresc = ' | '.join(komunikaty)
        for is_raport, gml_val in [(False, gml_id), (True, f"{gml_id}|{tresc}")]:
            feat = QgsFeature()
            feat.setFields(schema_fields)
            feat.setAttribute("klasa", class_name)
            feat.setAttribute("gml_id", gml_val)
            feat.setAttribute("komunikat", f"topo_e5_k6 Kontrola cyklu życia: niespójność dat - {tresc}")
            try:
                feat.setGeometry(QgsGeometry())
            except:
                pass
            if is_raport:
                obiektyZbledami.append(feat)
            else:
                nowe_bledy.append(feat)
    global vl_cykl_zycia_global
    if nowe_bledy and vl_cykl_zycia_global is None:
        vl_cykl_zycia_global=QgsVectorLayer("None?crs=EPSG:2180","cykl_zycia_BDOT10k","memory")
        pr=vl_cykl_zycia_global.dataProvider()
        pr.addAttributes([QgsField("klasa",QVariant.String),QgsField("gml_id",QVariant.String),QgsField("komunikat",QVariant.String)])
        vl_cykl_zycia_global.updateFields()
        QgsProject.instance().addMapLayer(vl_cykl_zycia_global)
    if nowe_bledy:
        try: vl_cykl_zycia_global.dataProvider().addFeatures(nowe_bledy); vl_cykl_zycia_global.updateExtents()
        except Exception as e: QgsMessageLog.logMessage(f"Nie udało się dodać błędów do warstwy pamięciowej: {e}", tag="Walidator plików GML", level=Qgis.Warning)
    return obiektyZbledami
    
    

def kontrolaZgodnosciZDanymiPRNG(layer, plik_prng, klasa):
    """
    Kontrola zgodności BDOT10k z danymi PRNG.
    Wykrywa: niezgodności nazw oraz niepołączone nadmiarowe w BDOT.
    """
    def get_attr(f, n, d=None): return f[n] if n in f.fields().names() else d
    global loaded_gml_prng_miejscowosci, loaded_gml_prng_obiektyfizjograficzne
    obiektyZbledami = []
    if 'identyfikatorPRNG' not in layer.fields().names(): return []
    is_adms = klasa in ('OT_ADMS_A','OT_ADMS_P')
    if is_adms:
        if loaded_gml_prng_miejscowosci is None and 'PRNG_MIEJSCOWOSCI' in plik_prng:
            loaded_gml_prng_miejscowosci = QgsVectorLayer(plik_prng, 'PRNG_MIEJSCOWOSCI','ogr')
        loaded = loaded_gml_prng_miejscowosci
    else:
        if loaded_gml_prng_obiektyfizjograficzne is None and 'PRNG_OBIEKTY_FIZJOGRAFICZNE' in plik_prng:
            loaded_gml_prng_obiektyfizjograficzne = QgsVectorLayer(plik_prng, 'PRNG_OBIEKTY_FIZJOGRAFICZNE','ogr')
        loaded = loaded_gml_prng_obiektyfizjograficzne
    if not loaded or not loaded.isValid(): return []
    extracted = processing.run("native:extractbyexpression",
        {'INPUT': layer,'EXPRESSION':'"identyfikatorPRNG" IS NOT NULL','OUTPUT':'memory:'})['OUTPUT']
    idx = extracted.fields().indexFromName('identyfikatorPRNG')
    if extracted.fields()[idx].type() != QVariant.Int:
        extracted = processing.run("native:fieldcalculator", {
            'INPUT': extracted,'FIELD_NAME':'identyfikatorPRNG_int','FIELD_TYPE':0,
            'FORMULA':'toint("identyfikatorPRNG")','OUTPUT':'memory:'})['OUTPUT']
        idx = extracted.fields().indexFromName('identyfikatorPRNG_int')
    join = processing.run("native:joinattributestable", {
        'INPUT': extracted,'FIELD': extracted.fields()[idx].name(),
        'INPUT_2': loaded,'FIELD_2':'identyfikatorPRNG',
        'FIELDS_TO_COPY':['identyfikatorPRNG','nazwaGlowna'],
        'METHOD':1,'DISCARD_NONMATCHING':False,
        'OUTPUT':'memory:','NON_MATCHING':'memory:'})
    out = join['OUTPUT']
    # —-- NIEZGODNOŚĆ NAZW
    for f in out.getFeatures():
        id_b = get_attr(f, extracted.fields()[idx].name())
        if id_b is None: continue
        naz_b = get_attr(f, 'nazwa')
        naz_p = get_attr(f, 'nazwaGlowna')
        if naz_b and naz_p and naz_b.strip() != naz_p.strip():
            i_gml = out.fields().indexFromName("gml_id")
            prefix = (get_attr(f,'gml_id','') + " | ") if get_attr(f,'gml_id') else ""
            f.setAttribute(i_gml, f"{prefix}Niezgodność nazw: {naz_p} vs {naz_b}")
            out.updateFeature(f)
            obiektyZbledami.append(f)
    # —-- OBIEKTY NIEPOŁĄCZONE (NADMIAROWE)
    for f in join['NON_MATCHING'].getFeatures():
        id_prng = get_attr(f, 'identyfikatorPRNG')
        if id_prng is None: continue
        gml = get_attr(f, 'gml_id', "nie dotyczy")
        f.setAttribute(f.fields().indexFromName("gml_id"),
                      f"{gml} | Obiekt niepołączony (brak w PRNG id:{id_prng})")
        obiektyZbledami.append(f)

    return obiektyZbledami


PRNG_found_in_SW_PTWP = set()
def kontrolaZgodnosciZDanymiPRNG_braki(layer, plik_prng, klasa):
    """
    Kontrola braków obiektów PRNG w ramach powiatu - DO INTERPRETACJI
    """
    def get_attr(f,n,d=None): return f[n] if n in f.fields().names() else d
    global loaded_gml_prng_miejscowosci, loaded_gml_prng_obiektyfizjograficzne, PRNG_found_in_SW_PTWP
    obiektyZbledami = []
    if 'identyfikatorPRNG' not in [f.name() for f in layer.fields()]: return []

    SW_map = {'OT_SWRS_L': {'potok','strumień','struga','rzeka','zatoka rzeki'},
              'OT_SWRM_L': {'rów'},
              'OT_SWKN_L': {'kanał'}}
    PTWP_types = {'zatoka rzeki','jezioro','morze','staw','zatoka','zatoka jeziora'}

    is_adms = klasa in ('OT_ADMS_A','OT_ADMS_P')
    loaded = loaded_gml_prng_miejscowosci if is_adms else loaded_gml_prng_obiektyfizjograficzne
    if not loaded or not loaded.isValid():
        loaded = QgsVectorLayer(plik_prng,'PRNG_MIEJSCOWOSCI' if is_adms else 'PRNG_OBIEKTY_FIZJOGRAFICZNE','ogr')
        if is_adms: loaded_gml_prng_miejscowosci = loaded
        else: loaded_gml_prng_obiektyfizjograficzne = loaded
        if not loaded or not loaded.isValid(): return []
    teryt = re.search(r"\.(\d{4})__", layer.name())
    teryt = teryt.group(1) if teryt else None
    if not teryt: return []

    cfg = configparser.ConfigParser()
    base = pathlib.Path(QgsApplication.qgisSettingsDirPath())/"python/plugins/Walidator_plikow_gml"
    cfg.read(str(base/"Walidator_plikow_gml.ini"))
    granice = QgsVectorLayer(cfg['DEFAULT']['granicepowiatow'],'GranicePowiatow','ogr')
    pow_geom = next((f.geometry() for f in granice.getFeatures() if str(f['JPT_KOD_JE'])==teryt), None)
    if not pow_geom: return []

    pow_mem = QgsVectorLayer(f"Polygon?crs={granice.crs().authid()}","pow","memory")
    f = QgsFeature(pow_mem.fields()); f.setGeometry(pow_geom.buffer(0.01,5))
    pow_mem.dataProvider().addFeature(f); pow_mem.updateExtents()
    prng_powiat = processing.run("native:clip",{'INPUT':loaded,'OVERLAY':pow_mem,'OUTPUT':'memory:'})['OUTPUT']

    bdot = processing.run("native:extractbyexpression",{'INPUT':layer,'EXPRESSION':'"identyfikatorPRNG" IS NOT NULL','OUTPUT':'memory:'})['OUTPUT']
    idx = bdot.fields().indexFromName('identyfikatorPRNG')
    if bdot.fields()[idx].type() != QVariant.Int:
        bdot = processing.run("native:fieldcalculator",{'INPUT':bdot,'FIELD_NAME':'identyfikatorPRNG_int','FIELD_TYPE':0,'FORMULA':'toint("identyfikatorPRNG")','OUTPUT':'memory:'})['OUTPUT']
        idx = bdot.fields().indexFromName('identyfikatorPRNG_int')
    bdot_ids = {get_attr(f, bdot.fields()[idx].name()) for f in bdot.getFeatures()}
            # ADMS
    if is_adms:
        prng_ok = [f for f in prng_powiat.getFeatures() if get_attr(f,'statusNazwy') in ('urzędowa','zestandaryzowana')]
        for f in prng_ok:
            ident = get_attr(f,'identyfikatorPRNG')
            if ident not in bdot_ids:
                f_err = QgsFeature(layer.fields())
                f_err.setAttribute('identyfikatorPRNG', ident)
                if 'nazwa' in layer.fields().names(): f_err.setAttribute('nazwa', get_attr(f,'nazwaGlowna'))
                f_err.setAttribute('gml_id', f"nie dotyczy | Brak obiektu w BDOT: {ident} {get_attr(f,'nazwaGlowna')} - DO INTERPRETACJI")
                obiektyZbledami.append(f_err)
    else:
        for f in prng_powiat.getFeatures():
            status = get_attr(f,'statusNazwy')
            if status not in ('urzędowa','zestandaryzowana'): continue
            ident, rodzaj = get_attr(f,'identyfikatorPRNG'), get_attr(f,'rodzajObiektu')
            if ident in bdot_ids or ident in PRNG_found_in_SW_PTWP: continue
            added = False
            # SW         
            for cls, typy in SW_map.items():
                if cls in layer.name() and rodzaj in typy:
                    f_err = QgsFeature(layer.fields())
                    f_err.setAttribute('identyfikatorPRNG', ident)
                    if 'nazwa' in layer.fields().names(): f_err.setAttribute('nazwa', get_attr(f,'nazwaGlowna'))
                    f_err.setAttribute('gml_id', f"nie dotyczy | Brak obiektu w BDOT: {ident} {get_attr(f,'nazwaGlowna')} - DO INTERPRETACJI")
                    obiektyZbledami.append(f_err)
                    PRNG_found_in_SW_PTWP.add(ident)
                    added = True
            # PTWP
            if layer.name().endswith('PTWP_A') and rodzaj in PTWP_types and not added:
                f_err = QgsFeature(layer.fields())
                f_err.setAttribute('identyfikatorPRNG', ident)
                if 'nazwa' in layer.fields().names(): f_err.setAttribute('nazwa', get_attr(f,'nazwaGlowna'))
                f_err.setAttribute('gml_id', f"nie dotyczy | Brak obiektu w BDOT: {ident} {get_attr(f,'nazwaGlowna')} - DO INTERPRETACJI")
                obiektyZbledami.append(f_err)
                PRNG_found_in_SW_PTWP.add(ident)

    return obiektyZbledami



def kontrolaZgodnosciZDanymiGDOS(layer, tc_zip, klasa):
    """
    Kontrola zgodności BDOT10k z danymi GDOŚ.
    Wykrywa niezgodności geometryczne, braki obiektów, rozbieżności nazw i identyfikatorów.
    """
    global tereny_chronione_zip
    obiektyZbledami = []
    try:
        valid_layer_names = ['OT_TCPN_A', 'OT_TCPK_A', 'OT_TCRZ_A', 'OT_TCON_A']
        if not layer or not layer.isValid(): return obiektyZbledami
        klasa = next((k for k in valid_layer_names if layer.name().endswith(k)), None)
        if not klasa: return obiektyZbledami
        klasaPliki = {'OT_TCPN_A':['ParkiNarodowePolygon.shp'],'OT_TCPK_A':['ParkiKrajobrazowePolygon.shp'],'OT_TCRZ_A':['RezerwatyPolygon.shp'],'OT_TCON_A':['ObszarySpecjalnejOchronyPolygon.shp','SpecjalneObszaryOchronyPolygon.shp']}
        if tereny_chronione_zip is None: tereny_chronione_zip = tc_zip
        rozpakowany_folder = os.path.dirname(tereny_chronione_zip)
        if zipfile.is_zipfile(tereny_chronione_zip):
            with zipfile.ZipFile(tereny_chronione_zip, 'r') as plikZIP: plikZIP.extractall(rozpakowany_folder)
        loaded_shp = {k: [] for k in valid_layer_names}
        def wczytaj_warstwe(sciezka, i, klasa):
            base = os.path.splitext(sciezka)[0]; cpg = base + ".cpg"
            if not os.path.exists(cpg):
                with open(cpg, "w", encoding="ascii") as f: f.write("CP1250")
            warstwa = QgsVectorLayer(sciezka, f"{klasa}_{i}_CP1250", "ogr")
            if not warstwa.isValid():
                try:
                    with open(cpg, "w", encoding="ascii") as f: f.write("UTF-8")
                    warstwa = QgsVectorLayer(sciezka + "|encoding=UTF-8", f"{klasa}_{i}_UTF8", "ogr")
                    if not warstwa.isValid():
                        with open(cpg, "w", encoding="ascii") as f: f.write("ISO-8859-2")
                        warstwa = QgsVectorLayer(sciezka + "|encoding=ISO-8859-2", f"{klasa}_{i}_ISO", "ogr")
                except Exception as e:
                    print(f"[WARN] Nie udało się naprawić kodowania: {e}")
            if not warstwa.isValid():
                print(f"[WARN] Nie udało się poprawnie odczytać: {os.path.basename(sciezka)}")
                return None
            return processing.run("native:fixgeometries", {'INPUT': warstwa, 'OUTPUT': 'memory:'})['OUTPUT']
        for i, plik in enumerate(klasaPliki[klasa]):
            sciezka = os.path.join(rozpakowany_folder, plik)
            warstwa = wczytaj_warstwe(sciezka, i, klasa)
            if warstwa: loaded_shp[klasa].append(warstwa)
        fix = lambda l: processing.run("native:fixgeometries", {'INPUT': l, 'OUTPUT': 'memory:'})['OUTPUT']
        posiada = fix(processing.run("native:extractbyexpression", {'INPUT': layer, 'EXPRESSION': '"numerCRFOP" IS NOT NULL', 'OUTPUT': 'memory:'})['OUTPUT'])
        shp_out = fix(processing.run("native:extractbyexpression", {'INPUT': loaded_shp[klasa][0], 'EXPRESSION': "not lower(\"nazwa\") like '%otulina%'", 'OUTPUT': 'memory:'})['OUTPUT']) if klasa!='OT_TCON_A' else fix(processing.run("native:mergevectorlayers", {'LAYERS': loaded_shp[klasa], 'OUTPUT':'memory:'})['OUTPUT'])
        config = configparser.ConfigParser(); mainPath = pathlib.Path(QgsApplication.qgisSettingsDirPath()) / "python/plugins/Walidator_plikow_gml"
        config.read(str(mainPath / "Walidator_plikow_gml.ini"))
        granicePowiatow = QgsVectorLayer(config['DEFAULT']['granicepowiatow'], 'GranicePowiatow', 'ogr')
        match = re.search(r"\.(\d{4})__", layer.name()); 
        if not match: return obiektyZbledami
        teryt = match.group(1)
        powiat_geom = next((f.geometry() for f in granicePowiatow.getFeatures() if str(f['JPT_KOD_JE']) == teryt), None)
        if powiat_geom is None: return obiektyZbledami
        powiat_geom_bufor = powiat_geom.buffer(-0.2, 5)
        pow_layer = QgsVectorLayer("Polygon?crs=epsg:2180&field=kod:string", "powiat_bufor_minus20", "memory")
        feat = QgsFeature(pow_layer.fields()); feat.setGeometry(powiat_geom_bufor); feat['kod'] = teryt
        pow_layer.dataProvider().addFeature(feat); pow_layer.updateExtents()
        clip = fix(processing.run("native:clip", {'INPUT': shp_out, 'OVERLAY': pow_layer, 'OUTPUT': 'memory:'})['OUTPUT'])
        shp_linie = fix(processing.run("native:polygonstolines", {'INPUT': clip, 'OUTPUT': 'memory:'})['OUTPUT'])
        pos_linie = fix(processing.run("native:polygonstolines", {'INPUT': posiada, 'OUTPUT': 'memory:'})['OUTPUT'])
        bufor = fix(processing.run("native:buffer", {'INPUT': shp_linie, 'DISTANCE': 0.4, 'SEGMENTS': 10, 'DISSOLVE': True, 'OUTPUT': 'memory:'})['OUTPUT'])
        bufor_przyciety = fix(processing.run("native:clip", {'INPUT': bufor, 'OVERLAY': pow_layer, 'OUTPUT': 'memory:'})['OUTPUT'])
        diff = fix(processing.run("native:difference", {'INPUT': pos_linie, 'OVERLAY': bufor_przyciety, 'OUTPUT': 'memory:'})['OUTPUT'])
        pojedyncze = [f for f in processing.run("native:multiparttosingleparts", {'INPUT': diff, 'OUTPUT': 'memory:'})['OUTPUT'].getFeatures() if f.geometry() and f.geometry().within(powiat_geom_bufor)]
        if pojedyncze:
            print(f"[INFO] {len(pojedyncze)} geometrii GDOŚ wymagało naprawy, kontrola będzie przeprowadzona.")
            poza = QgsVectorLayer("LineString?crs=epsg:2180&field=gml_id:string(254)", f"niezgodność geometrii {klasa} z danymi GDOŚ", "memory")
            poza.startEditing()
            for feat in pojedyncze:
                geom = feat.geometry(); przyp = next((f for f in posiada.getFeatures() if f.geometry().intersects(geom)), None)
                gml_id = przyp['gml_id'] if przyp and 'gml_id' in przyp.fields().names() else 'nie dotyczy'
                nowy = QgsFeature(poza.fields()); nowy.setGeometry(geom); nowy['gml_id'] = f"{gml_id} | niezgodność geometrii {klasa} z danymi GDOŚ"
                poza.addFeature(nowy); obiektyZbledami.append(nowy)
            poza.commitChanges(); QgsProject.instance().addMapLayer(poza)
        kodinsp_bdot = {f['numerCRFOP'] for f in posiada.getFeatures() if f['numerCRFOP']}
        braki_layer = QgsVectorLayer("Polygon?crs=epsg:2180&field=trescBledu:string&field=gml_id:string(254)", f"Braki {klasa} w BDOT", "memory"); braki_dp = braki_layer.dataProvider()
        for f in clip.getFeatures():
            if f['nazwa'] and 'otulina' in f['nazwa'].lower(): continue
            kod_gdos = f['kodinspire']
            if not kod_gdos or kod_gdos in kodinsp_bdot: continue
            geom_f = f.geometry()
            geometrie = [g.geometry() for g in posiada.getFeatures() if g['numerCRFOP'] == kod_gdos]
            if any(g.intersection(geom_f).area() / geom_f.area() > 0.7 for g in geometrie): continue
            feat_err = QgsFeature(braki_layer.fields()); feat_err.setGeometry(geom_f)
            feat_err['trescBledu'] = f"Brak obiektu z danych GDOŚ: {kod_gdos}"; feat_err['gml_id'] = f"{kod_gdos} kodinspire"; braki_dp.addFeature(feat_err)
            feat_err_tab = QgsFeature(braki_layer.fields()); feat_err_tab.setGeometry(geom_f); feat_err_tab['gml_id'] = f"nie dotyczy | Brak obiektu z danych GDOŚ: {kod_gdos}"; obiektyZbledami.append(feat_err_tab)
        if braki_layer.featureCount() > 0: QgsProject.instance().addMapLayer(braki_layer)
        join = processing.run("native:joinattributestable", {'INPUT': posiada,'FIELD':'numerCRFOP','INPUT_2':clip,'FIELD_2':'kodinspire','FIELDS_TO_COPY':['nazwa','kod'],'METHOD':1,'DISCARD_NONMATCHING':False,'PREFIX':'GDOS_','OUTPUT':'memory:','NON_MATCHING':'memory:'})
        def normalize_unicode(s):
            if not isinstance(s, str): return s
            if s is None: return ""
            s = s.strip().replace('\xa0',' ').replace('\u200b','')
            for enc, dec in [('cp1250','utf-8'),('latin1','iso-8859-2'),('utf-8','utf-8')]:
                try:
                    t = s.encode(enc,'ignore').decode(dec,'ignore')
                    if len(t) >= len(s): s = t; break
                except Exception: pass
            return unicodedata.normalize("NFC", s)
        normalize_compare = lambda t: normalize_unicode(t).casefold() if t else ""
        for f in join['OUTPUT'].getFeatures():
            n1, n2 = normalize_compare(f['nazwa']), normalize_compare(f['GDOS_nazwa'])
            zmiany = []
            if n1 and n2 and n1 != n2: zmiany.append(f"nazwa: BDOT='{normalize_unicode(f['nazwa'])}', GDOŚ='{normalize_unicode(f['GDOS_nazwa'])}'")
            if klasa == 'OT_TCON_A' and 'kodNatura2000' in f.fields().names() and f['kodNatura2000'] and f['GDOS_kod'] and f['kodNatura2000'] != f['GDOS_kod']:
                zmiany.append(f"kod: kodNatura2000='{f['kodNatura2000']}', GDOS_kod='{f['GDOS_kod']}'")
            if zmiany and f.geometry():
                buf_minus, buf_plus = powiat_geom.buffer(-0.2, 5), powiat_geom.buffer(0.2, 5)
                if f.geometry().within(buf_plus) and not f.geometry().intersects(buf_minus): continue
                if f.geometry().intersects(buf_minus):
                    old_id = str(f["gml_id"]) if "gml_id" in f.fields().names() and f["gml_id"] else "nie dotyczy"
                    f["gml_id"] = f"{old_id} | {'; '.join(zmiany)}"; join['OUTPUT'].updateFeature(f); obiektyZbledami.append(f)
        if join['NON_MATCHING'].featureCount() > 0:
            join['NON_MATCHING'].setName(f"obiekty {klasa} nie połączone z GDOŚ"); QgsProject.instance().addMapLayer(join['NON_MATCHING'])
    except Exception as e:
        print(f"Błąd przy wykonaniu topo_e1_k122_1: {e}")
    return obiektyZbledami


def kontrolaFormatuAtrybutuWysokoscRT(layer, plikGML):
    """
    Kontrola formatu atrybutu 'wysokosc' w klasie RT.
    Weryfikuje zgodność wartości z określonymi patternami dla różnych rodzajów obiektów.
    """
    obiektyZbledami = []
    pattern_kopiec = re.compile(r"^-?\d+\.(0|5)$")
    pattern_punkt = re.compile(r"^-?\d+\.\d$")
    pattern_poziomica = re.compile(r"^-?\d+\.(00|25|50|75)$")    
    plikGML = plikGML.getroot()
    otklasa = f".//ot:{layer.name()[-9:]}"
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        "ot": "urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0"
    }   
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        try:
            match = featureMember.find(otklasa, namespaces=ns)
            if match is None:
                continue           
            lokalnyId_elem = match.find('.//ot:lokalnyId', namespaces=ns)
            wysokosc_elem = match.find('.//ot:wysokosc', namespaces=ns)
            rodzaj_elem = match.find('.//ot:rodzaj', namespaces=ns)
            if lokalnyId_elem is None or wysokosc_elem is None or rodzaj_elem is None:
                continue           
            lokalnyId = (lokalnyId_elem.text or "").strip()
            wysokosc = (wysokosc_elem.text or "").strip()
            rodzaj = (rodzaj_elem.text or "").strip()          
            if lokalnyId not in lokalnyId_to_feature:
                continue
            feature = lokalnyId_to_feature[lokalnyId]            
            if rodzaj == 'skarpa' and wysokosc == '-999':
                continue
            if not wysokosc or wysokosc in ['-0.0', '-0.00']:
                obiektyZbledami.append(feature)
                continue
            if rodzaj in ['dół', 'kopiec lub hałda', 'skarpa', 'wąwóz']:
                if not pattern_kopiec.fullmatch(wysokosc):
                    obiektyZbledami.append(feature)
            elif rodzaj == 'punkt wysokościowy w terenie':
                if not pattern_punkt.fullmatch(wysokosc):
                    obiektyZbledami.append(feature)
            elif rodzaj == 'poziomica':
                if not pattern_poziomica.fullmatch(wysokosc):
                    obiektyZbledami.append(feature)
        except Exception:
            obiektyZbledami.append(feature)
    
    return obiektyZbledami


def kontrolaFormatuAtrybutuSzerokoscSW(layer, plikGML):
    obiektyZbledami = []
    pattern = re.compile(r"^\d+\.([05])$")
    plikGML = plikGML.getroot()
    otklasa = f'.//ot:{layer.name()[-9:]}'
    ns = {'gml': 'http://www.opengis.net/gml/3.2', "ot": "urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0"}
    
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is not None:
            lokalnyId = matches.find('.//ot:lokalnyId', namespaces=ns)
            szerokosc_elem = matches.find('.//ot:szerokosc', namespaces=ns)
            lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
            
            if lokalnyId_text not in lokalnyId_to_feature or szerokosc_elem is None:
                continue
            
            feature = lokalnyId_to_feature[lokalnyId_text]
            szerokosc_value = (szerokosc_elem.text or "").strip()
            if not pattern.fullmatch(szerokosc_value):
                obiektyZbledami.append(feature)
    
    return obiektyZbledami


def kontrolaFormatuAtrybutuSzerokoscSK(layer, plikGML):
    obiektyZbledami = []
    
    klasa = layer.name()[-9:]  
    valid_layer_names = ['OT_SKJZ_L', 'OT_SKDR_L', 'OT_SKRP_L']   
    if klasa not in valid_layer_names:
        return []
    
    atrybut_do_sprawdzenia = "szerokoscNawierzchni" if klasa in ['OT_SKJZ_L', 'OT_SKDR_L'] else "szerokosc"    
    pattern = re.compile(r"^\d+\.\d$")  
    
    plikGML = plikGML.getroot()
    otklasa = f'.//ot:{klasa}'
    ns = {'gml': 'http://www.opengis.net/gml/3.2', "ot": "urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0"}
    
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is not None:
            lokalnyId = matches.find('.//ot:lokalnyId', namespaces=ns)
            szerokosc_elem = matches.find(f'.//ot:{atrybut_do_sprawdzenia}', namespaces=ns)
            lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
            
            if lokalnyId_text not in lokalnyId_to_feature or szerokosc_elem is None:
                continue
            
            feature = lokalnyId_to_feature[lokalnyId_text]
            szerokosc_value = (szerokosc_elem.text or "").strip()
            if not pattern.fullmatch(szerokosc_value):
                obiektyZbledami.append(feature)
    
    return obiektyZbledami


def kontrolaFormatuAtrybutuWysokoscZapory(layer, plikGML):
    """
    Kontrola formatu atrybutu 'wysokoscZapory' w klasie BUHD.
    Wykrywa brak wartości lub błąd precyzji zapisu (dla zapory).
    """    
    obiektyZbledami = []
    pattern = re.compile(r"^[1-9]\d*$")
    plikGML = plikGML.getroot()
    otklasa = f'.//ot:{layer.name()[-9:]}'
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'}
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    for fm in plikGML.findall('.//gml:featureMember', ns):
        obiekt = fm.find(otklasa, ns)
        if obiekt is None: continue
        rodzaj_elem = obiekt.find('.//ot:rodzaj', ns)
        if rodzaj_elem is None or (rodzaj_elem.text or "").strip() != "zapora":
            continue
        lokalnyId = obiekt.find('.//ot:lokalnyId', ns)
        lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
        if lokalnyId_text not in lokalnyId_to_feature: continue
        feature = lokalnyId_to_feature[lokalnyId_text]
        wys_elem = obiekt.find('.//ot:wysokoscZapory', ns)
        wys_val = (wys_elem.text.strip() if (wys_elem is not None and wys_elem.text) else "")
        if not pattern.fullmatch(wys_val):
            obiektyZbledami.append(feature)
    return obiektyZbledami



def kontrolaFormatuAtrybutuWysokoscBUWT(layer, plikGML):
    """
    Kontrola formatu atrybutu 'wysokosc' w klasie BUWT.
    Wykrywa brak wartości lub błąd precyzji zapisu.
    """
    obiektyZbledami = []
    pattern = re.compile(r"^[1-9]\d*$")
    plikGML = plikGML.getroot()
    otklasa = f'.//ot:{layer.name()[-9:]}'
    ns = {'gml': 'http://www.opengis.net/gml/3.2', "ot": "urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0"}
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is None:
            continue
        lokalnyId = matches.find('.//ot:lokalnyId', namespaces=ns)
        wysokosc_elem = matches.find('.//ot:wysokosc', namespaces=ns)
        lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
        if lokalnyId_text not in lokalnyId_to_feature:
            continue
        feature = lokalnyId_to_feature[lokalnyId_text]
        wysokosc_value = (wysokosc_elem.text.strip() if (wysokosc_elem is not None and wysokosc_elem.text) else "")
        if not pattern.fullmatch(wysokosc_value):
            obiektyZbledami.append(feature)
    return obiektyZbledami



def kontrolaFormatuParametrowBUZM(layer, plikGML):
    obiektyZbledami = []
    pattern = re.compile(r"^\d+\.[05]$")
    plikGML = plikGML.getroot()
    otklasa = f'.//ot:{layer.name()[-9:]}'
    ns = {'gml': 'http://www.opengis.net/gml/3.2', "ot": "urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0"}
    
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is not None:
            lokalnyId = matches.find('.//ot:lokalnyId', namespaces=ns)
            wysokosc_elem = matches.find('.//ot:wysokosc', namespaces=ns)
            szerokoscPodstawy_elem = matches.find('.//ot:szerokoscPodstawy', namespaces=ns)
            szerokoscKorony_elem = matches.find('.//ot:szerokoscKorony', namespaces=ns)
            lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
            
            if lokalnyId_text not in lokalnyId_to_feature:
                continue
            
            feature = lokalnyId_to_feature[lokalnyId_text]
            komunikaty_bledu = []
            
            for attr_name, elem in [
                ('wysokosc', wysokosc_elem),
                ('szerokoscPodstawy', szerokoscPodstawy_elem),
                ('szerokoscKorony', szerokoscKorony_elem)
            ]:
                if elem is not None:
                    value = (elem.text or "").strip()
                    if not pattern.fullmatch(value):
                        komunikaty_bledu.append(f"{attr_name}")
            
            if komunikaty_bledu:
                gml_id = str(feature.attribute("gml_id")) if feature.attribute("gml_id") is not None else 'NULL'
                gml_id = (gml_id + " | " if gml_id != 'NULL' else "")  + ", ".join(komunikaty_bledu)
                feature.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
                layer.updateFeature(feature)
                obiektyZbledami.append(feature)
    
    return obiektyZbledami


def kontrolaFormatuParametrowBUIN(layer, plikGML):
    """
    Kontrola formatu atrybutów BUIN:
    - 'nosnosc' dla mostów, estakad, wiaduktów (="eksploatowany")
    - 'szerokosc' dla wszystkich (!= "w budowie") poza przejściami podziemnymi i dla zwierząt.
    Wykrywa brak wartości lub błędną precyzję.
    """
    obiektyZbledami = []

    pattern_nosnosc = re.compile(r"^[1-9]\d*$")
    pattern_szerokosc = re.compile(r"^\d+\.\d$")
    plikGML = plikGML.getroot()
    otklasa = f'.//ot:{layer.name()[-9:]}'
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        "ot": "urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0"
    }
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    for fm in plikGML.findall('.//gml:featureMember', ns):
        matches = fm.find(otklasa, ns)
        if matches is None:
            continue
        lokalnyId = matches.find('.//ot:lokalnyId', ns)
        rodzaj_elem = matches.find('.//ot:rodzaj', ns)
        nosnosc_elem = matches.find('.//ot:nosnosc', ns)
        szerokosc_elem = matches.find('.//ot:szerokosc', ns)
        lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
        if lokalnyId_text not in lokalnyId_to_feature:
            continue
        feature = lokalnyId_to_feature[lokalnyId_text]
        kategoria = feature["kategoriaIstnienia"] if "kategoriaIstnienia" in feature.fields().names() and feature["kategoriaIstnienia"] else None
        eksploatowany = kategoria.strip().lower() == "eksploatowany" if kategoria else False
        komunikaty_bledu = []
        if rodzaj_elem is not None and rodzaj_elem.text in {"most", "estakada", "wiadukt"}:
            if nosnosc_elem is not None and nosnosc_elem.text:
                if not pattern_nosnosc.fullmatch(nosnosc_elem.text.strip()):
                    komunikaty_bledu.append("nosnosc")
            elif eksploatowany:
                komunikaty_bledu.append("nosnosc")
        if rodzaj_elem is not None and rodzaj_elem.text not in {"przejście podziemne dla pieszych", "przejście dla zwierząt"}:
            if szerokosc_elem is not None and szerokosc_elem.text:
                if not pattern_szerokosc.fullmatch(szerokosc_elem.text.strip()):
                    komunikaty_bledu.append("szerokosc")
            elif (feature["kategoriaIstnienia"] or "").strip().lower() != "w budowie":
                komunikaty_bledu.append("szerokosc")
        if komunikaty_bledu:
            gml_id = str(feature.attribute("gml_id")) if feature.attribute("gml_id") is not None else 'NULL'
            gml_id = (gml_id + " | " if gml_id != 'NULL' else "") + ", ".join(komunikaty_bledu)
            feature.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
            layer.updateFeature(feature)
            obiektyZbledami.append(feature)

    return obiektyZbledami



def kontrolaFormatuAtrybutuSzerokoscBUTR(layer, plikGML):
    obiektyZbledami = []
    pattern = re.compile(r"^[1-9]\d*$")
    plikGML = plikGML.getroot()
    otklasa = f'.//ot:{layer.name()[-9:]}'
    ns = {'gml': 'http://www.opengis.net/gml/3.2', "ot": "urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0"}
    
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is not None:
            lokalnyId = matches.find('.//ot:lokalnyId', namespaces=ns)
            wysokosc_elem = matches.find('.//ot:szerokosc', namespaces=ns)
            lokalnyId_text = lokalnyId.text if lokalnyId is not None else "Nieznane ID"
            
            if lokalnyId_text not in lokalnyId_to_feature or wysokosc_elem is None:
                continue
            
            feature = lokalnyId_to_feature[lokalnyId_text]
            wysokosc_value = (wysokosc_elem.text or "").strip()
            if not pattern.fullmatch(str(wysokosc_value)):
                obiektyZbledami.append(feature)
    
    return obiektyZbledami



def kontrolaBialeZnaki(layer, plikGML):
    """
    Kontrola występowania niepożądanych białych znaków w atrybutach opisowych BDOT10k:
    na początku, końcu lub wewnątrz tekstu.
    """    
    obiektyZbledami = []
    atrybuty_opisowe = ["nazwa", "uwagi", "informacjaDodatkowa", "oznaczenieZmiany", "liczbaMieszkancow", "liczbaKondygnacji", "numerDrogi", "nazwaDrogi", "szerokoscNawierzchni", "szerokosc", "wysokosc", "szerokoscKorony", "szerokoscPodstawy", "nosnosc", "identyfikatorPRNG", "identyfikatorMPHP", "identyfikatorSIMC", "identyfikatorTERC", "identyfikatorULIC", "identyfikatorTERYTjednostki", "idTERYTjednostkiNadrzednej", "identyfikatorEGiB", "kodKst"]
    dostępne_pola = [field.name() for field in layer.fields()]
    aktywne_atrybuty = [p for p in atrybuty_opisowe if p in dostępne_pola]
    if not aktywne_atrybuty:
        return obiektyZbledami
    plikGML = plikGML.getroot()
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'
    }
    otklasa = f'.//ot:{layer.name()[-9:]}'
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}

    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is None:
            continue
        lokalnyId_elem = matches.find('.//ot:lokalnyId', namespaces=ns)
        if lokalnyId_elem is None or not lokalnyId_elem.text:
            continue
        lokalnyId_text = lokalnyId_elem.text.strip()
        feature = lokalnyId_to_feature.get(lokalnyId_text)
        if feature is None:
            continue
        komunikaty_bledu = []
        for pole in aktywne_atrybuty:
            elem = matches.find(f'.//ot:{pole}', namespaces=ns)
            if elem is not None and elem.text is not None:
                tekst = elem.text
                if tekst.strip() != tekst or not tekst.strip() or re.search(r"\s{2,}", tekst):
                    komunikaty_bledu.append(pole)
            elif elem is not None and elem.text is None:
                komunikaty_bledu.append(pole)
        if komunikaty_bledu:
            gml_id = feature.attribute("gml_id") or "NULL"
            nowy_gml_id = (gml_id + " | " if gml_id != "NULL" else "") + "Niepożądane białe znaki w atrybucie: " + ", ".join(komunikaty_bledu)
            feature.setAttribute(layer.fields().indexFromName("gml_id"), nowy_gml_id)
            layer.updateFeature(feature)
            obiektyZbledami.append(feature)

    return obiektyZbledami


def kontrolaPusteZnaczniki(layer, plikGML):
    obiektyZbledami = []
    atrybuty_kontrolowane= [
        "nazwa", "uwagi", "informacjaDodatkowa", "oznaczenieZmiany", "liczbaMieszkancow", "liczbaKondygnacji",
        "numerDrogi", "nazwaDrogi", "szerokoscNawierzchni", "szerokosc", "wysokosc", "szerokoscKorony",
        "szerokoscPodstawy", "nosnosc", "identyfikatorPRNG", "identyfikatorMPHP", "identyfikatorSIMC",
        "identyfikatorTERC", "identyfikatorULIC", "identyfikatorTERYTjednostki", "idTERYTjednostkiNadrzednej",
        "identyfikatorEGiB", "kodKst", "kodKarto10k", "pomnikPrzyrody", "skrotKartograficzny"
    ]
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'
    }
    otklasa = f'.//ot:{layer.name()[-9:]}'
    root = plikGML.getroot()
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    for featureMember in root.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is None:
            continue
        lokalnyId_elem = matches.find('.//ot:lokalnyId', namespaces=ns)
        if lokalnyId_elem is None or not lokalnyId_elem.text:
            continue
        lokalnyId_text = lokalnyId_elem.text.strip()
        feature = lokalnyId_to_feature.get(lokalnyId_text)
        if feature is None:
            continue
        komunikaty_bledu = []
        for pole in atrybuty_kontrolowane:
            elem = matches.find(f'ot:{pole}', namespaces=ns)
            if elem is not None:
                tekst = elem.text
                if tekst is None or tekst.strip() == "":
                    komunikaty_bledu.append(pole)
        if komunikaty_bledu:
            gml_id = feature.attribute("gml_id") or "NULL"
            nowy_gml_id = (gml_id + " | " if gml_id != "NULL" else "") + "Pusty element: " + ", ".join(komunikaty_bledu)
            feature.setAttribute(layer.fields().indexFromName("gml_id"), nowy_gml_id)
            layer.updateFeature(feature)
            obiektyZbledami.append(feature)

    return obiektyZbledami
    

def kontrolaAtrybutuNazwa(layer, plikGML):
    obiektyZbledami = []
    if 'nazwa' not in [field.name() for field in layer.fields()]:
        return obiektyZbledami
    plikGML = plikGML.getroot()
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'
    }
    otklasa = f'.//ot:{layer.name()[-9:]}'
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    wzorzec_min_dwie_litery = re.compile(r'[a-ząćęłńóśźżĄĆĘŁŃÓŚŹŻ]{2,}', re.IGNORECASE)
    
    for featureMember in plikGML.findall('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa, namespaces=ns)
        if matches is None:
            continue
        lokalnyId_elem = matches.find('.//ot:lokalnyId', namespaces=ns)
        lokalnyId_text = (lokalnyId_elem.text or "").strip() if lokalnyId_elem is not None else ""
        feature = lokalnyId_to_feature.get(lokalnyId_text)
        if not feature:
            continue
        nazwa_elem = matches.find('.//ot:nazwa', namespaces=ns)
        if nazwa_elem is None:
            continue
        nazwa_text = (nazwa_elem.text or "").strip()
        if nazwa_text and not wzorzec_min_dwie_litery.search(nazwa_text):
            wartosc_bledu = nazwa_text
            gml_id = str(feature['gml_id']) if feature['gml_id'] is not None else 'NULL'
            gml_id = (gml_id + "|" if gml_id != 'NULL' else "") + f": {wartosc_bledu}"
            feature.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
            layer.updateFeature(feature)
            obiektyZbledami.append(feature)
    
    return obiektyZbledami


def kontrolaReferencjiBDOT500GESUT(layer, plikGML):
    obiektyZbledami = []
    if "OT_RT" in layer.name():
        return obiektyZbledami
    valid_layer_names_gesut = ['OT_SUPR_L', 'OT_SULN_L', 'OT_BUIT_P', 'OT_BUIT_A']
    klasa = layer.name()[-9:]
    is_gesut_layer = klasa in valid_layer_names_gesut
    ns = {
        'gml': 'http://www.opengis.net/gml/3.2',
        'ot': 'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'
    }
    otklasa_xpath = f".//ot:{klasa}"
    lokalnyId_to_feature = {f["lokalnyId"]: f for f in layer.getFeatures()}
    root = plikGML.getroot()
    for featureMember in root.xpath('.//gml:featureMember', namespaces=ns):
        matches = featureMember.find(otklasa_xpath, namespaces=ns)
        if matches is None:
            continue
        lokalnyId_elem = matches.find('.//ot:lokalnyId', namespaces=ns)
        if lokalnyId_elem is None or not lokalnyId_elem.text:
            continue
        lokalnyId_text = lokalnyId_elem.text.strip()
        feature = lokalnyId_to_feature.get(lokalnyId_text)
        if feature is None:
            continue
        komunikaty_bledu = []
        # BDOT500
        bdot500_elem = matches.find('.//ot:BDOT500', namespaces=ns)
        if bdot500_elem is not None:
            ref = bdot500_elem.find('.//ot:OT_ReferencjaDoObiektu', namespaces=ns)
            if ref is not None:
                lokalnyId_ref = ref.find('.//ot:lokalnyId', namespaces=ns)
                przestrzenNazw_ref = ref.find('.//ot:przestrzenNazw', namespaces=ns)
                if (lokalnyId_ref is None or not lokalnyId_ref.text or not lokalnyId_ref.text.strip()) or \
                   (przestrzenNazw_ref is None or not przestrzenNazw_ref.text or not przestrzenNazw_ref.text.strip()):
                    komunikaty_bledu.append("BDOT500")
        # GESUT
        if is_gesut_layer:
            gesut_elem = matches.find('.//ot:GESUT', namespaces=ns)
            if gesut_elem is not None:
                ref = gesut_elem.find('.//ot:OT_ReferencjaDoObiektu', namespaces=ns)
                if ref is not None:
                    lokalnyId_ref = ref.find('.//ot:lokalnyId', namespaces=ns)
                    przestrzenNazw_ref = ref.find('.//ot:przestrzenNazw', namespaces=ns)
                    if (lokalnyId_ref is None or not lokalnyId_ref.text or not lokalnyId_ref.text.strip()) or \
                       (przestrzenNazw_ref is None or not przestrzenNazw_ref.text or not przestrzenNazw_ref.text.strip()):
                        komunikaty_bledu.append("GESUT")
        if komunikaty_bledu:
            gml_id = str(feature.attribute("gml_id")) if feature.attribute("gml_id") else 'NULL'
            nowy_gml_id = (gml_id + " | " if gml_id != 'NULL' else "") + ", ".join(komunikaty_bledu)
            feature.setAttribute(layer.fields().indexFromName("gml_id"), nowy_gml_id)
            layer.updateFeature(feature)
            obiektyZbledami.append(feature)
            
    return obiektyZbledami


def kontrolaSpojnosciSKDR_SKJZ(layer):
    try:
        def normalize_attr(val, attr_name=None):
            if val is None:
                return ''
            if isinstance(val, (list, tuple)):
                vals = [str(v).strip() for v in val if v is not None]
                if attr_name == 'numerDrogi':
                    vals.sort()
                return ','.join(vals)
            return str(val).strip()
        obiektyZbledami = []
        dodane_bledy_ids = set()
        if not layer.isValid():
            return []
        skjz_layer_name = layer.name().replace("OT_SKDR_L", "OT_SKJZ_L")
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        if not skjz_layers:
            return []
        skjz_layer = skjz_layers[0]
        skrw_layer_name = layer.name().replace("OT_SKDR_L", "OT_SKRW_P")
        skrw_layers = QgsProject.instance().mapLayersByName(skrw_layer_name)
        if not skrw_layers:
            return []
        skrw_layer = skrw_layers[0]
        index_skjz = QgsSpatialIndex()
        skjz_dict = {}
        for f in skjz_layer.getFeatures():
            geom = f.geometry()
            if geom and not geom.isEmpty():
                index_skjz.insertFeature(f)
                skjz_dict[f.id()] = {
                    "geometry": geom,
                    "attributes": {attr: f[attr] for attr in f.fields().names()}
                }
        index_skrw = QgsSpatialIndex()
        ronda_dict = {}
        for f in skrw_layer.getFeatures():
            if str(f['rodzaj']).lower() == 'rondo':
                geom = f.geometry()
                if geom and not geom.isEmpty():
                    index_skrw.insertFeature(f)
                    ronda_dict[f.id()] = geom
        wspolne_atrybuty = [
            'kategoriaIstnienia', 'kategoriaZarzadzania', 'klasaDrogi',
            'materialNawierzchni', 'polozenie', 'szerokoscNawierzchni',
            'numerDrogi', 'zrodloDanychGeometrycznych', 'nazwaDrogi'
        ]
        for f in layer.getFeatures():
            komunikaty_bledu = []
            geom = f.geometry()
            if not geom or geom.isEmpty():
                continue
            liczba_jezdni = f['liczbaJezdniDrogi']
            try:
                liczba_jezdni = int(liczba_jezdni)
            except:
                liczba_jezdni = None
            candidate_ids = index_skjz.intersects(geom.boundingBox())
            matching_found = False
            for cid in candidate_ids:
                skjz_geom = skjz_dict[cid]["geometry"]
                if skjz_geom.intersects(geom):
                    intersection = skjz_geom.intersection(geom)
                    if intersection.length() > 0:
                        matching_found = True
                        skjz_attrs = skjz_dict[cid]["attributes"]
                        for attr in wspolne_atrybuty:
                            if attr == 'szerokoscNawierzchni' and liczba_jezdni != 1:
                                continue
                            try:
                                val_skdr = normalize_attr(f[attr], attr_name=attr)
                                val_skjz = normalize_attr(skjz_attrs[attr], attr_name=attr)
                            except:
                                continue
                            if val_skdr != val_skjz:
                                komunikaty_bledu.append(f"{attr}: SKDR='{val_skdr}' ≠ SKJZ='{val_skjz}'")
                        break
            if not matching_found and liczba_jezdni == 1:
                info_dodatkowa = str(f['informacjaDodatkowa']) if 'informacjaDodatkowa' in f.fields().names() else ''
                uwagi = str(f['uwagi']) if 'uwagi' in f.fields().names() else ''
                if "linia umowna" in info_dodatkowa.lower() or "linia umowna" in uwagi.lower():
                    continue
                skrw_found = False
                geom_buffer = geom.buffer(50, 5)
                candidate_ids_skrw = index_skrw.intersects(geom_buffer.boundingBox())
                for cid in candidate_ids_skrw:
                    skrw_geom = ronda_dict[cid]
                    if skrw_geom.buffer(50, 5).intersects(geom):
                        skrw_found = True
                        break
                if skrw_found:
                    continue
                try:
                    start_pt = geom.constGet().startPoint()
                    end_pt = geom.constGet().endPoint()
                    from qgis.core import QgsPointXY, QgsGeometry
                    for pt in [start_pt, end_pt]:
                        buf = QgsGeometry.fromPointXY(QgsPointXY(pt)).buffer(1, 5)
                        candidate_ids_skjz = index_skjz.intersects(buf.boundingBox())
                        intersecting_skjz = [cid for cid in candidate_ids_skjz if skjz_dict[cid]["geometry"].intersects(buf)]
                        if len(intersecting_skjz) >= 3:
                            # rozgałęzienie SKJZ – nie traktuj jako błąd
                            raise StopIteration
                except StopIteration:
                    continue
                except Exception:
                    pass                 
                komunikaty_bledu.append("niespójność geometrii lub brak odpowiednika w OT_SKJZ_L")
            if komunikaty_bledu:
                gml_id = str(f['gml_id']) if f['gml_id'] is not None else 'NULL'
                gml_id = (gml_id + " | " if gml_id != 'NULL' else "") + ", ".join(komunikaty_bledu)
                f.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
                layer.updateFeature(f)
                if f.id() not in dodane_bledy_ids:
                    obiektyZbledami.append(f)
                    dodane_bledy_ids.add(f.id())
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w kontroli: {e}")
        return []


def sztucznyL_SW(layer):
    try:
        obiektyZbledami = []
        dodane_ids = set()
        valid_layer_names = ['OT_SWRS_L', 'OT_SWKN_L']
        if not layer or not layer.isValid(): return []
        if not any(layer.name().endswith(n) for n in valid_layer_names): return []

        ptwp_layer = next((l for l in QgsProject.instance().mapLayers().values() 
                           if l.name().endswith("OT_PTWP_A")), None)
        if not ptwp_layer: return []

        ptwp_index = QgsSpatialIndex()
        ptwp_geometrie = {}
        for feat in ptwp_layer.getFeatures():
            if str(feat["rodzaj"]).strip().lower() == "woda stojąca":
                geom = feat.geometry()
                if geom and not geom.isEmpty():
                    inner_buf = geom.buffer(-0.02, 8) if geom.area() > 0 else geom
                    ptwp_index.insertFeature(feat)
                    ptwp_geometrie[feat.id()] = inner_buf

        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty(): continue

            candidate_ids = ptwp_index.intersects(geom.boundingBox())
            for pid in candidate_ids:
                buf_geom = ptwp_geometrie.get(pid)
                if buf_geom:
                    intersect = geom.intersection(buf_geom)
                    if intersect and not intersect.isEmpty():
                        cecha = ""
                        if "cechaGeometrii" in feat.fields().names():
                            cecha_val = feat["cechaGeometrii"]
                            cecha = str(cecha_val).strip().lower() if cecha_val else ""
                        if cecha != "sztuczny łącznik":
                            nowy_feat = QgsFeature()
                            nowy_feat.setFields(layer.fields())
                            nowy_feat.setGeometry(intersect)
                            nowy_feat.initAttributes(len(layer.fields()))
                            for i in range(len(layer.fields())):
                                nowy_feat.setAttribute(i, feat[i])
                            gml_idx = layer.fields().indexFromName("gml_id")
                            if gml_idx >= 0:
                                nowy_feat.setAttribute(gml_idx, str(feat['gml_id']))
                            obiektyZbledami.append(nowy_feat)
                        break

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w funkcji sztucznyL_SW: {e}")
        return []


def liniaUmowna_SK(layer):
    try:
        obiektyZbledami = []
        dodane_ids = set()
        valid_layer_name = 'OT_SKJZ_L'
        tol = 0.02
        if not layer or not layer.isValid(): return []
        if not layer.name().endswith(valid_layer_name): return []
        ptpl_layer = next((l for l in QgsProject.instance().mapLayers().values() 
                           if l.name().endswith("OT_PTPL_A")), None)
        if not ptpl_layer: return []
        ptpl_index = QgsSpatialIndex()
        ptpl_geometrie = {}
        for feat in ptpl_layer.getFeatures():
            geom = feat.geometry()
            if geom and not geom.isEmpty():
                ptpl_index.insertFeature(feat)
                ptpl_geometrie[feat.id()] = geom
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty(): continue
            cecha = str(feat["cechaGeometrii"]).strip().lower() if feat["cechaGeometrii"] else ""
            klasaDrogi = str(feat["klasaDrogi"]).strip().lower() if feat["klasaDrogi"] else ""
            if klasaDrogi not in ["droga lokalna", "droga dojazdowa", "droga wewnętrzna"]:
                continue

            candidate_ids = ptpl_index.intersects(geom.boundingBox())
            for pid in candidate_ids:
                plac_geom = ptpl_geometrie.get(pid)
                if plac_geom:
                    inner_buf = plac_geom.buffer(-tol, 2)
                    intersect = geom.intersection(inner_buf)
                    if intersect and not intersect.isEmpty() and cecha != "linia umowna":
                        nowy_feat = QgsFeature()
                        nowy_feat.setFields(layer.fields())
                        nowy_feat.setGeometry(intersect)
                        nowy_feat.initAttributes(len(layer.fields()))
                        for i in range(len(layer.fields())):
                            nowy_feat.setAttribute(i, feat[i])
                        gml_idx = layer.fields().indexFromName("gml_id")
                        if gml_idx >= 0:
                            nowy_feat.setAttribute(gml_idx, str(feat['gml_id']))
                        obiektyZbledami.append(nowy_feat)
                        dodane_ids.add(feat.id())
                    break
        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w funkcji liniaUmowna_SK: {e}")
        return []


def zawieranieSWRM(layer):
    try:
        obiektyZbledami = []
        dodane_ids = set()
        valid_layer_name = 'OT_SWRM_L'
        tol = 0.02
        if not layer or not layer.isValid(): return []
        if not layer.name().endswith(valid_layer_name): return []
        ptwp_layer = next((l for l in QgsProject.instance().mapLayers().values() 
                           if l.name().endswith("OT_PTWP_A")), None)
        if not ptwp_layer: return []

        ptwp_index = QgsSpatialIndex()
        ptwp_geometrie = {}
        for feat in ptwp_layer.getFeatures():
            geom = feat.geometry()
            if geom and not geom.isEmpty():
                ptwp_index.insertFeature(feat)
                ptwp_geometrie[feat.id()] = geom

        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty(): continue

            candidate_ids = ptwp_index.intersects(geom.boundingBox())
            for pid in candidate_ids:
                ptwp_geom = ptwp_geometrie.get(pid)
                if ptwp_geom:
                    inner_buf = ptwp_geom.buffer(-tol, 2)
                    intersect = geom.intersection(inner_buf)
                    if intersect and not intersect.isEmpty():
                        nowy_feat = QgsFeature()
                        nowy_feat.setFields(layer.fields())
                        nowy_feat.setGeometry(intersect)
                        nowy_feat.initAttributes(len(layer.fields()))
                        for i in range(len(layer.fields())):
                            nowy_feat.setAttribute(i, feat[i])
                        gml_idx = layer.fields().indexFromName("gml_id")
                        if gml_idx >= 0:
                            nowy_feat.setAttribute(gml_idx, str(feat['gml_id']))
                        obiektyZbledami.append(nowy_feat)
                        dodane_ids.add(feat.id())
                    break

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w funkcji zawieranieSWRM: {e}")
        return []


def zawieranieOI(layer):
    try:
        obiektyZbledami = []
        dodane_ids = set()
        if not layer or not layer.isValid(): return []
        klasy_OI = ("OT_OIPR_L", "OT_OIPR_P", "OT_OIMK_A")
        klasa_layer = layer.name()[-9:]
        if klasa_layer not in klasy_OI: return []
        warstwy_powierzchniowe_nazwy = ["OT_PTLZ_A", "OT_PTZB_A", "OT_PTUT_A", "OT_PTRK_A", "OT_PTPL_A", "OT_PTWP_A"]
        warstwy_powierzchniowe = {}
        for lyr in QgsProject.instance().mapLayers().values():
            for nazwa in warstwy_powierzchniowe_nazwy:
                if lyr.name().endswith(nazwa): warstwy_powierzchniowe[nazwa] = lyr
        indeksy = {}
        geometrie = {}
        for nazwa, lyr in warstwy_powierzchniowe.items():
            idx = QgsSpatialIndex()
            geom_dict = {}
            for feat in lyr.getFeatures():
                geom = feat.geometry()
                if geom and not geom.isEmpty():
                    idx.insertFeature(feat)
                    geom_dict[feat.id()] = geom
            indeksy[nazwa] = idx
            geometrie[nazwa] = geom_dict
        raport_bledu = []
        warunki_OIPR_P = {
            "OT_PTLZ_A": lambda rodzaj, pomnik: rodzaj in ["drzewo lub grupa drzew", "kępa krzewów lub kosodrzewiny", "mały las"] and (pomnik == "obiekt niebędący pomnikiem przyrody" or not pomnik),
            "OT_PTZB_A": lambda rodzaj, pomnik: rodzaj not in ["drzewo lub grupa drzew", "mały las", "źródło", "kępa krzewów lub kosodrzewiny", "odosobniona skała", "głaz narzutowy lub grupa głazów"],
            "OT_PTUT_A": lambda rodzaj, pomnik: rodzaj in ["drzewo lub grupa drzew", "kępa krzewów lub kosodrzewiny", "mały las"] and (pomnik == "obiekt niebędący pomnikiem przyrody" or not pomnik),
            "OT_PTRK_A": lambda rodzaj, pomnik: rodzaj in ["drzewo lub grupa drzew", "kępa krzewów lub kosodrzewiny", "mały las"] and (pomnik == "obiekt niebędący pomnikiem przyrody" or not pomnik),
            "OT_PTPL_A": lambda rodzaj, pomnik: rodzaj not in ["drzewo lub grupa drzew", "głaz narzutowy lub grupa głazów"]
        }
        warunki_OIPR_L = {
            "OT_PTLZ_A": lambda rodzaj, pomnik: rodzaj in ["pas krzewów lub żywopłot", "rząd drzew"] and (pomnik == "obiekt niebędący pomnikiem przyrody" or not pomnik),
            "OT_PTZB_A": lambda rodzaj, pomnik: rodzaj not in ["pas krzewów lub żywopłot", "rząd drzew"],
            "OT_PTUT_A": lambda rodzaj, pomnik: rodzaj in ["wodospad", "próg skalny", "linia oddziałowa"] and (pomnik == "obiekt niebędący pomnikiem przyrody" or not pomnik),
            "OT_PTRK_A": lambda rodzaj, pomnik: rodzaj in ["pas krzewów lub żywopłot", "rząd drzew"] and (pomnik == "obiekt niebędący pomnikiem przyrody" or not pomnik),
            "OT_PTPL_A": lambda rodzaj, pomnik: rodzaj in ["wodospad", "próg skalny", "linia oddziałowa"] and (pomnik == "obiekt niebędący pomnikiem przyrody" or not pomnik)
        }
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty(): continue
            rodzaj = (feat["rodzaj"] or "").strip().lower()
            wartosc_pomnik = ''
            if 'pomnikPrzyrody' in feat.fields().names(): wartosc_pomnik = feat.attribute('pomnikPrzyrody') or ''
            pomnik = wartosc_pomnik.strip().lower()
            id_feat = feat.id()
            komunikaty_bledu = []
            warstwy_docelowe = ["OT_PTLZ_A", "OT_PTZB_A", "OT_PTUT_A", "OT_PTRK_A", "OT_PTPL_A", "OT_PTWP_A"]
            for nazwa_warstwy in warstwy_docelowe:
                if nazwa_warstwy not in indeksy: continue
                kandydaci = indeksy[nazwa_warstwy].intersects(geom.boundingBox())
                for cid in kandydaci:
                    geom_cel = geometrie[nazwa_warstwy].get(cid)
                    if not geom_cel: continue
                    if geom.within(geom_cel):
                        if id_feat in dodane_ids: break
                        bledny = False
                        if klasa_layer == "OT_OIPR_P":
                            if nazwa_warstwy in warunki_OIPR_P and warunki_OIPR_P[nazwa_warstwy](rodzaj, pomnik):
                                bledny = True
                        elif klasa_layer == "OT_OIPR_L":
                            if nazwa_warstwy in warunki_OIPR_L and warunki_OIPR_L[nazwa_warstwy](rodzaj, pomnik):
                                bledny = True
                        if bledny:
                            komunikaty_bledu.append(f"obiekt typu '{rodzaj}' błędnie na warstwie '{nazwa_warstwy}'")
                            dodane_ids.add(id_feat)
                        break
            if klasa_layer == "OT_OIMK_A":
                if "OT_PTWP_A" in indeksy:
                    kandydaci = indeksy["OT_PTWP_A"].intersects(geom.boundingBox())
                    for cid in kandydaci:
                        geom_cel = geometrie["OT_PTWP_A"].get(cid)
                        if geom_cel and geom.within(geom_cel):
                            if id_feat not in dodane_ids:
                                komunikaty_bledu.append(f"obiekt typu '{rodzaj}' błędnie na warstwie 'OT_PTWP_A'")
                                dodane_ids.add(id_feat)
                            break
            if komunikaty_bledu:
                tekst_bledu = "; ".join(komunikaty_bledu)
                gml_id = str(feat['gml_id']) if 'gml_id' in feat.fields().names() else 'NULL'
                if gml_id != 'NULL': gml_id = f"{gml_id} | {tekst_bledu}"
                feat.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
                layer.updateFeature(feat)
                obiektyZbledami.append(feat)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w funkcji zawieranieOI: {e}")
        return []


def zawieranieOIPR_glaz(layer):
    """
    Kontrola zawierania obiektów OIPR_P typu ‘odosobniona skała’ lub ‘głaz narzutowy’ na PTZB_A – do interpretacji
    """
    try:
        obiektyZinterpretacja = []
        if not layer or not layer.isValid(): return []
        if not layer.name().endswith("OT_OIPR_P"): return []
        warstwa_ptzb = None
        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.name().endswith("OT_PTZB_A"):
                warstwa_ptzb = lyr
                break
        if not warstwa_ptzb: return []
        idx_ptzb = QgsSpatialIndex()
        geometrie_ptzb = {}
        for feat in warstwa_ptzb.getFeatures():
            geom = feat.geometry()
            if geom and not geom.isEmpty():
                idx_ptzb.insertFeature(feat)
                geometrie_ptzb[feat.id()] = geom
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty(): continue
            rodzaj = (feat["rodzaj"] or "").strip().lower()
            if rodzaj not in ["odosobniona skała", "głaz narzutowy lub grupa głazów"]:
                continue
            kandydaci = idx_ptzb.intersects(geom.boundingBox())
            for cid in kandydaci:
                geom_cel = geometrie_ptzb.get(cid)
                if geom_cel and geom.within(geom_cel):
                    komunikat = f"obiekt typu '{rodzaj}' znajduje się na warstwie 'OT_PTZB_A' – DO INTERPRETACJI"
                    gml_id = str(feat['gml_id']) if 'gml_id' in feat.fields().names() else 'NULL'
                    if gml_id != 'NULL': gml_id = f"{gml_id} | {komunikat}"
                    feat.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
                    layer.updateFeature(feat)
                    obiektyZinterpretacja.append(feat)
                    break

        return obiektyZinterpretacja
    except Exception as e:
        print(f"Błąd w funkcji zawieranieOIPR_glaz: {e}")
        return []
        

def przecinanieRTLW_SK_BUZM(layer):
    """
    Kontrola przecinania się skarp i wąwozów z liniowymi warstwami SK, SW i BUZM.
    Wykrywa przecinanie wewnątrz linii (styki wierzchołkami i współliniowość są ignorowane).
    """
    try:
        obiektyZbledami = []
        dodane_ids = set()
        valid_layer_name = 'OT_RTLW_L'
        if not layer or not layer.isValid() or not layer.name().endswith(valid_layer_name):
            return []
        warstwy_docelowe = ['OT_SKJZ_L', 'OT_SKDR_L', 'OT_SKTR_L', 'OT_SKRP_L',
                            'OT_BUZM_L', 'OT_SWKN_L', 'OT_SWRS_L', 'OT_SWRM_L']
        sk_layers = {'OT_SKJZ_L', 'OT_SKDR_L', 'OT_SKTR_L', 'OT_SKRP_L'}
        sw_layers = {'OT_SWKN_L', 'OT_SWRS_L', 'OT_SWRM_L'}
        indeksy = {}
        for nazwa in warstwy_docelowe:
            w = next((l for l in QgsProject.instance().mapLayers().values() if l.name().endswith(nazwa)), None)
            if w:
                idx = QgsSpatialIndex()
                geometrie = {}
                for f in w.getFeatures():
                    lname = nazwa.split("__")[-1]
                    if lname in sk_layers:
                        if f["polozenie"] != "na powierzchni gruntu":
                            continue
                        if f.fields().indexOf("rodzaj") >= 0 and str(f["rodzaj"]).strip().lower() == "ścieżka":
                            continue
                    elif lname in sw_layers:
                        if f["polozenie"] != "na powierzchni":
                            continue
                    g = f.geometry()
                    if g and not g.isEmpty():
                        idx.insertFeature(f)
                        geometrie[f.id()] = g
                indeksy[nazwa] = (idx, geometrie)
        for feat in layer.getFeatures():
            geom = feat.geometry()
            if not geom or geom.isEmpty():
                continue
            rodzaj = str(feat['rodzaj']).strip().lower() if feat['rodzaj'] else ""
            if rodzaj not in ('skarpa', 'wąwóz') or not feat['kodKarto10k']:
                continue
            kolizje = []
            for nazwa, (idx, geometrie) in indeksy.items():
                for pid in idx.intersects(geom.boundingBox()):
                    cel_geom = geometrie.get(pid)
                    if cel_geom:
                        if geom.crosses(cel_geom):
                            kolizje.append(nazwa)
                            break
            if kolizje:
                komunikat = f"obiekt typu '{rodzaj}' błędnie przecina " + ", ".join(kolizje)
                gml_val = str(feat['gml_id']) if feat['gml_id'] else 'NULL'
                nowa_wartosc = (gml_val + " | " if gml_val != 'NULL' else "") + komunikat
                feat.setAttribute(layer.fields().indexFromName("gml_id"), nowa_wartosc)
                layer.updateFeature(feat)
                if feat.id() not in dodane_ids:
                    obiektyZbledami.append(feat)
                    dodane_ids.add(feat.id())

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w funkcji przecinanieRTLW_SK_BUZM: {e}")
        return []


def przecinanieRTLW(layer):
    """
    Kontrola przecinania w OT_RTLW_L: poziomica–poziomica, skarpa–skarpa, wąwóz–wąwóz.
    Ignoruje zamknięte linie i styki krańców linii. Zapisuje punkty kolizji.
    """
    obiektyZbledami=[]
    tolerance=0.02
    lokalizator=QgsVectorLayer("Point?crs=EPSG:2180","lokalizator_kolizji_RTLW_L","memory")
    dp_loc=lokalizator.dataProvider()
    dp_loc.addAttributes([QgsField("gml_id",QVariant.String),QgsField("obiekt w kolizji",QVariant.String)])
    lokalizator.updateFields()
    def dodaj_punkt(p, gml, obiekt_w_kolizji):
        feat = QgsFeature(lokalizator.fields())
        feat.setGeometry(QgsGeometry.fromPointXY(p))
        feat.setAttributes([gml, obiekt_w_kolizji])
        dp_loc.addFeature(feat)
    def get_attr(f,name):
        try:
            if name in f.fields().names():
                v=f[name]
                if v is None: return "__MISSING__"
                s=str(v).strip().lower()
                return s if s else "__MISSING__"
            return "__MISSING__"
        except: return "__MISSING__"
    try:
        if not layer or not layer.isValid() or not layer.name().endswith("OT_RTLW_L"): return []
        if layer.geometryType()!=QgsWkbTypes.LineGeometry: return []
        def is_closed_line(g):
            try:
                pts=g.asPolyline() if not g.isMultipart() else g.asMultiPolyline()[0]
                return bool(pts) and pts[0]==pts[-1]
            except: return False
        dop=["poziomica","skarpa","wąwóz"]
        feats=[(f,get_attr(f,"rodzaj"),get_attr(f,"wysokosc")) for f in layer.getFeatures() if get_attr(f,"rodzaj") in dop]
        if not feats: return []
        feats_by_r={r:{} for r in dop}
        for f,r,w in feats: feats_by_r[r][f.id()] = (f,r,w)
        idx_gml=layer.fields().indexFromName("gml_id")
        kolizje=set()
        for rodzaj in dop:
            group=feats_by_r[rodzaj]
            idx=QgsSpatialIndex()
            ends={}
            for f,r,w in group.values():
                g=f.geometry()
                if not g or g.isEmpty() or is_closed_line(g): continue
                idx.insertFeature(f)
                pts=g.asPolyline() if not g.isMultipart() else g.asMultiPolyline()[0]
                ends[f.id()] = (QgsPointXY(pts[0]),QgsPointXY(pts[-1]),w)
            for fid,(e1s,e1e,w1) in ends.items():
                f1,r1,w1=group[fid]
                g1=f1.geometry()
                for cid in idx.intersects(g1.boundingBox()):
                    if cid<=fid or cid not in ends: continue
                    if (fid,cid) in kolizje: continue
                    kolizje.add((fid,cid))
                    f2,r2,w2=group[cid]
                    g2=f2.geometry()
                    e2s,e2e,w2=ends[cid]
                    if rodzaj=="poziomica":
                        styka=any(e1.distance(e2)<=tolerance for e1 in [e1s,e1e] for e2 in [e2s,e2e])
                        if styka and w1==w2: continue
                        try:
                            inter=g1.intersection(g2)
                            if not inter.isEmpty():
                                obiektyZbledami.extend([f1,f2])
                                if inter.type()==QgsWkbTypes.PointGeometry:
                                    pts=[inter.asPoint()] if not inter.isMultipart() else [p for part in inter.asMultiPoint() for p in part]
                                    for p in pts: dodaj_punkt(p,f1["gml_id"],r2)
                        except: obiektyZbledami.extend([f1,f2])
                        continue
                    if rodzaj in ["skarpa","wąwóz"]:
                        try:
                            inter=g1.intersection(g2)
                            if inter.isEmpty(): continue
                            if inter.type()==QgsWkbTypes.PointGeometry:
                                pts=[inter.asPoint()] if not inter.isMultipart() else [p for part in inter.asMultiPoint() for p in part]
                                for p in pts:
                                    p1s=p.distance(e1s)>tolerance and p.distance(e1e)>tolerance
                                    p2s=p.distance(e2s)>tolerance and p.distance(e2e)>tolerance
                                    if p1s and p2s:
                                        obiektyZbledami.extend([f1,f2])
                                        dodaj_punkt(p,f1["gml_id"],r2)
                                        break
                        except: pass
        obiektyZbledami=list({f.id():f for f in obiektyZbledami}.values())
        layer.startEditing()
        for f in obiektyZbledami:
            gml=f["gml_id"] or ""
            rodz=get_attr(f,"rodzaj")
            f.setAttribute(idx_gml,f"{gml}| {rodz}")
            layer.updateFeature(f)
        layer.commitChanges()
        if dp_loc.featureCount()>0:
            lokalizator.updateExtents()
            QgsProject.instance().addMapLayer(lokalizator)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w funkcji przecinanieRTLW dla warstwy {layer.name()}: {e}")
        return []
        

def przecinanieOT(layer):
    """
    Kontrola przecinania się obiektów powierzchniowych w warstwach OT_BU*.
    """
    from qgis.core import QgsSpatialIndex, QgsWkbTypes
    obiektyZbledami = []
    try:
        if not layer or not layer.isValid():
            return []
        klasa = layer.name()[-9:]
        if not (klasa.startswith("OT_BU") or klasa.startswith("OT_KU")):
            return []
        if layer.geometryType() != QgsWkbTypes.PolygonGeometry:
            return []
        features = list(layer.getFeatures())
        id2feature = {f.id(): f for f in features}
        lokalnyId2feature = {f['lokalnyId']: f for f in features}
        sprawdzone = set()
        idx = QgsSpatialIndex()
        for f in features:
            idx.insertFeature(f)
        for f in features:
            geom = f.geometry()
            if not geom or geom.isEmpty():
                continue
            rodzaj_feat = (f["rodzaj"] or "").strip().lower() if 'rodzaj' in f.fields().names() else None
            for cid in idx.intersects(geom.boundingBox()):
                if cid == f.id():
                    continue
                if cid not in id2feature:
                    continue
                feat2 = id2feature[cid]
                lokalnyId1 = f['lokalnyId']
                lokalnyId2 = feat2['lokalnyId']
                if lokalnyId1 == lokalnyId2:
                    continue
                para = tuple(sorted((lokalnyId1, lokalnyId2)))
                if para in sprawdzone:
                    continue
                sprawdzone.add(para)

                geom2 = feat2.geometry()
                if not geom2 or geom2.isEmpty():
                    continue
                if klasa.startswith("OT_KU"):
                    rodzaj2 = (feat2["rodzaj"] or "").strip().lower() if 'rodzaj' in feat2.fields().names() else None
                    if rodzaj_feat != rodzaj2:
                        continue
                if geom.intersects(geom2):
                    overlap = geom.intersection(geom2)
                    if overlap and not overlap.isEmpty():
                        overlap_area = overlap.area() if hasattr(overlap, "area") else 0
                        wspolna_dlugosc = overlap.length() if hasattr(overlap, "length") and overlap.length() > 0 else 0.0001
                        szerokosc = overlap_area / wspolna_dlugosc
                        if overlap_area > 1.0 and szerokosc > 0.04:
                            obiektyZbledami.extend([f, feat2])

        return list({f['lokalnyId']: f for f in obiektyZbledami}.values())

    except Exception as e:
        print(f"Błąd w funkcji przecinanieOT dla warstwy {layer.name()}: {e}")
        return []
   

def kolizjeSW(layer):
    """
    Kontrola nakładania się cieków SWRS / SWKN / SWRM.
    Wykrywa fragmenty obiektów z nakładaniem liniowym w ramach klasy i pomiedzy klasami SW.
    """
    obiektyZbledami, tol, min_len = [], 0.04, 0.04
    try:
        if not layer or not layer.isValid(): return []
        layers = QgsProject.instance().mapLayers().values()
        get = lambda suf: next((l for l in layers if l.name().endswith(suf)), None)
        lname = layer.name()
        swrs, swrm, swkn = get("OT_SWRS_L"), get("OT_SWRM_L"), get("OT_SWKN_L")
        if lname.endswith("OT_SWRS_L"): same, others = layer, [l for l in (swrm, swkn) if l]
        elif lname.endswith("OT_SWRM_L"): same, others = layer, [l for l in (swkn,) if l]
        elif lname.endswith("OT_SWKN_L"): same, others = layer, []
        else: return []
        added = set()
        def add(f, g, other_layer_name=None):
            if g.isEmpty(): return
            k = (f.id(), round(g.length(), 6))
            if k in added: return
            added.add(k)
            nf = QgsFeature(f)
            nf.setGeometry(g)
            if other_layer_name and 'gml_id' in layer.fields().names():
                short = other_layer_name[-9:]
                old_val = nf['gml_id'] if nf['gml_id'] else ""
                nf.setAttribute('gml_id', f"{old_val} | Kolizja z {short}")
            obiektyZbledami.append(nf)
        def chk(f1, f2, other_layer_name=None):
            g1, g2 = f1.geometry(), f2.geometry()
            if g1.isEmpty() or g2.isEmpty(): return
            try:
                if g1.touches(g2): return
            except: pass
            inter = g1.intersection(g2)
            if inter and not inter.isEmpty():
                try:
                    if inter.type() == QgsWkbTypes.LineGeometry and inter.length() >= min_len:
                        add(f1, inter, other_layer_name); return
                    if inter.isMultipart():
                        for p in inter.asGeometryCollection():
                            if p.length() >= min_len: add(f1, p, other_layer_name); return
                except:
                    if inter.length() >= min_len: add(f1, inter, other_layer_name); return
        feats = list(same.getFeatures())
        idx = QgsSpatialIndex()
        idx.addFeatures(feats)
        fmap = {f.id(): f for f in feats}
        for f1 in feats:
            g1 = f1.geometry()
            if g1.isEmpty(): continue
            for fid2 in idx.intersects(g1.boundingBox()):
                if fid2 <= f1.id(): continue
                f2 = fmap[fid2]
                if f1['lokalnyId'] != f2['lokalnyId']:
                    chk(f1, f2)
        main = list(layer.getFeatures())
        for l2 in others:
            feats2 = list(l2.getFeatures())
            idx2 = QgsSpatialIndex()
            idx2.addFeatures(feats2)
            fmap2 = {f.id(): f for f in feats2}
            other_name = l2.name()
            for f1 in main:
                g1 = f1.geometry()
                if g1.isEmpty(): continue
                for fid2 in idx2.intersects(g1.boundingBox()):
                    f2 = fmap2[fid2]
                    if f1['lokalnyId'] != f2['lokalnyId']:
                        chk(f1, f2, other_layer_name=other_name)

        return obiektyZbledami

    except Exception as e:
        print("Błąd kolizjeSW:", e)
        return []


def kolizjeLiniiOT(layer):
    """
    Kontrola nakładania liniowego w obrębie jednej warstwy liniowej (_L).
    Zwraca listę QgsFeature z geometrią nakładającego się fragmentu.
    Nie działa dla warstw SW (SWRS/SWRM/SWKN).
    """
    from qgis.core import QgsFeature, QgsSpatialIndex, QgsWkbTypes
    obiektyZbledami, min_len = [], 0.04
    try:
        if not layer or not layer.isValid(): return []
        name = layer.name()
        if not name.endswith("_L"): return []
        if name.endswith(("SWRS_L","SWRM_L","SWKN_L","OT_RTLW_L")): return []
        feats = list(layer.getFeatures())
        idx = QgsSpatialIndex()
        [idx.insertFeature(f) for f in feats]
        fmap = {f.id(): f for f in feats}
        added = set()
        def add(f, g):
            if g.isEmpty() or g.length() < min_len: return
            k = (f.id(), round(g.length(),6))
            if k in added: return
            added.add(k)
            nf = QgsFeature(f)
            nf.setGeometry(g)
            obiektyZbledami.append(nf)
        for f1 in feats:
            g1 = f1.geometry()
            if not g1 or g1.isEmpty(): continue
            candidates = idx.intersects(g1.boundingBox())
            for fid2 in candidates:
                if fid2 <= f1.id(): continue
                f2 = fmap[fid2]
                if f1['lokalnyId'] == f2['lokalnyId']: continue
                g2 = f2.geometry()
                if not g2 or g2.isEmpty(): continue
                try: 
                    if g1.touches(g2): continue
                except: pass
                inter = g1.intersection(g2)
                if inter and not inter.isEmpty():
                    try:
                        if inter.type() == QgsWkbTypes.LineGeometry and inter.length() >= min_len:
                            add(f1, inter)
                        elif inter.isMultipart():
                            for p in inter.asGeometryCollection():
                                if p.length() >= min_len: add(f1, p)
                    except:
                        if inter.length() >= min_len: add(f1, inter)
        return obiektyZbledami
    except Exception as e:
        print("Błąd kolizjeLiniiOT:", e)
        return []


def kontrolaSkrot_bTran(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        if layer.name()[-9:] != 'OT_KUKO_A':
            return []
        bubd_layer = next((l for l in QgsProject.instance().mapLayers().values() if l.name().endswith("OT_BUBD_A")), None)
        if not bubd_layer:
            return []
        index_bubd = QgsSpatialIndex()
        bubd_dict = {}
        for f in bubd_layer.getFeatures():
            if get_attr(f, "przewazajacaFunkcjaBudynku") in ["zajezdnia autobusowa", "zajezdnia tramwajowa", "zajezdnia trolejbusowa"]:
                geom = f.geometry()
                if geom and not geom.isEmpty():
                    index_bubd.insertFeature(f)
                    bubd_dict[f.id()] = f
        for f in layer.getFeatures():
            skrot = get_attr(f, "skrotKartograficzny")
            rodzaj = get_attr(f, "rodzaj")
            info = get_attr(f, "informacjaDodatkowa")
            nazwa = get_attr(f, "nazwa")
            geom = f.geometry()
            if not geom or geom.isEmpty():
                continue
            warunki_spelnione = rodzaj == "zajezdnia lub baza transportowa" and ("transport" in info or "transport" in nazwa)
            zawiera_zajezdnie = any(geom.contains(bubd_dict[fid].geometry()) for fid in index_bubd.intersects(geom.boundingBox()) if bubd_dict.get(fid)) if warunki_spelnione else False
            if warunki_spelnione and not zawiera_zajezdnie:
                if skrot != "b. tran.":
                    obiektyZbledami.append(f)
            elif skrot == "b. tran.":
                obiektyZbledami.append(f)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w kontroli skrótu 'b. tran.': {e}")
        return []


def kontrolaSkrot_zaj(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUKO_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuko_layer = layers.get("OT_KUKO_A")
        if not kuko_layer:
            return []
        funkcje_zajezdniowe = {"zajezdnia autobusowa", "zajezdnia tramwajowa", "zajezdnia trolejbusowa"}
        index_kuko, kuko_dict = QgsSpatialIndex(), {}
        for f in kuko_layer.getFeatures():
            if get_attr(f, "rodzaj") == "zajezdnia lub baza transportowa" and f.geometry() and not f.geometry().isEmpty():
                index_kuko.insertFeature(f)
                kuko_dict[f.id()] = f
        if klasa == "OT_KUKO_A":
            bubd_layer = layers.get("OT_BUBD_A")
            if not bubd_layer:
                return []
            index_bubd, bubd_dict = QgsSpatialIndex(), {}
            for f in bubd_layer.getFeatures():
                if get_attr(f, "przewazajacaFunkcjaBudynku") in funkcje_zajezdniowe and f.geometry() and not f.geometry().isEmpty():
                    index_bubd.insertFeature(f)
                    bubd_dict[f.id()] = f
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                geom = f.geometry()
                if not geom:
                    continue
                if rodzaj != "zajezdnia lub baza transportowa":
                    if skrot == "zaj.":
                        obiektyZbledami.append(f)
                    continue
                buffer_geom = geom.buffer(0.5, 5)
                found = any(
                    buffer_geom.contains(bubd_dict[fid].geometry())
                    for fid in index_bubd.intersects(buffer_geom.boundingBox())
                    if fid in bubd_dict
                )
                if (found and skrot != "zaj.") or (not found and skrot == "zaj."):
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or funkcja not in funkcje_zajezdniowe or istnienie == "zniszczony":
                    if skrot == "zaj.":
                        obiektyZbledami.append(f)
                    continue
                on_kuko = any(
                    kuko_dict[fid].geometry().buffer(0.5, 5).contains(geom)
                    for fid in index_kuko.intersects(geom.boundingBox())
                    if fid in kuko_dict
                )
                if (not on_kuko and skrot != "zaj.") or (on_kuko and skrot == "zaj."):
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'zaj.': {e}")
        return []


def kontrolaSkrot_el(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUPG_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kupg_layer, bubd_layer = layers.get("OT_KUPG_A"), layers.get("OT_BUBD_A")
        if not kupg_layer or not bubd_layer:
            return []
        index_kupg, kupg_dict = QgsSpatialIndex(), {}
        for f in kupg_layer.getFeatures():
            if get_attr(f, "rodzaj") == "elektrownia" and f.geometry() and not f.geometry().isEmpty():
                index_kupg.insertFeature(f)
                kupg_dict[f.id()] = f
        if klasa == "OT_KUPG_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if not f.geometry() or rodzaj != "elektrownia":
                    if skrot == "el.":
                        obiektyZbledami.append(f)
                    continue
                if skrot != "el.":
                    obiektyZbledami.append(f)
        else:
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or istnienie == "zniszczony" or funkcja != "elektrownia":
                    if skrot == "el.":
                        obiektyZbledami.append(f)
                    continue
                na_kupg = any(
                    kupg_dict[fid].geometry().buffer(0, 5).contains(geom)
                    for fid in index_kupg.intersects(geom.boundingBox())
                    if kupg_dict.get(fid)
                )
                if (not na_kupg and skrot != "el.") or (na_kupg and skrot == "el."):
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'el.': {e}")
        return []


def kontrolaSkrot_elc(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUPG_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kupg_layer, bubd_layer = layers.get("OT_KUPG_A"), layers.get("OT_BUBD_A")
        if not kupg_layer or not bubd_layer:
            return []
        index_kupg, kupg_dict = QgsSpatialIndex(), {}
        for f in kupg_layer.getFeatures():
            if get_attr(f, "rodzaj") == "elektrociepłownia" and f.geometry() and not f.geometry().isEmpty():
                index_kupg.insertFeature(f)
                kupg_dict[f.id()] = f
        if klasa == "OT_KUPG_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if rodzaj == "elektrociepłownia":
                    if skrot != "elc.":
                        obiektyZbledami.append(f)
                elif skrot == "elc.":
                    obiektyZbledami.append(f)
        else:
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or geom.isEmpty():
                    continue
                if funkcja == "elektrociepłownia" and istnienie != "zniszczony":
                    on_kupg = any(
                        kupg_dict[fid].geometry().buffer(0, 5).contains(geom)
                        for fid in index_kupg.intersects(geom.boundingBox())
                        if kupg_dict.get(fid)
                    )
                    if (not on_kupg and skrot != "elc.") or (on_kupg and skrot == "elc."):
                        obiektyZbledami.append(f)
                elif skrot == "elc.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'elc.': {e}")
        return []


def kontrolaSkrot_gar(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        if layer.name()[-9:] != "OT_BUBD_A": return []
        ptzb = [f for f in QgsProject.instance().mapLayers().values() 
                if f.name().endswith("OT_PTZB_A")]
        if not ptzb: return []
        ptzb = [f for f_layer in ptzb for f in f_layer.getFeatures() 
                if get_attr(f, "rodzaj") == "jednorodzinna" and f.geometry() and not f.geometry().isEmpty()]
        obiektyZbledami = []
        for f in layer.getFeatures():
            skrot = get_attr(f, "skrotKartograficzny")
            funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
            istnienie = get_attr(f, "kategoriaIstnienia")
            geom = f.geometry()
            if not geom or geom.isEmpty(): continue
            if funkcja == "garaż" and istnienie != "zniszczony":
                pow_bud = geom.area()
                pow_w_ptzb = sum(geom.intersection(p.geometry()).area() for p in ptzb if geom.intersects(p.geometry()))
                on_ptzb = pow_w_ptzb / pow_bud >= 0.9
                if (on_ptzb and skrot == "gar.") or (not on_ptzb and skrot != "gar."):
                    obiektyZbledami.append(f)
            elif skrot == "gar.":
                obiektyZbledami.append(f)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w kontroli skrótu 'gar.': {e}")
        return []




def kontrolaSkrot_gaz(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUPG_A', 'OT_BUBD_A', 'OT_BUIT_P']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kupg_layer = layers.get("OT_KUPG_A")
        if not kupg_layer:
            return []
        index_kupg, kupg_dict = QgsSpatialIndex(), {}
        for f in kupg_layer.getFeatures():
            if get_attr(f, "rodzaj") == "gazownia" and f.geometry() and not f.geometry().isEmpty():
                index_kupg.insertFeature(f)
                kupg_dict[f.id()] = f
        if klasa == "OT_KUPG_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if rodzaj == "gazownia":
                    if skrot != "gaz.":
                        obiektyZbledami.append(f)
                elif skrot == "gaz.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or istnienie == "zniszczony":
                    if skrot == "gaz.":
                        obiektyZbledami.append(f)
                    continue
                if funkcja in ["zbiornik na gaz", "stacja gazowa"]:
                    on_kupg = any(
                        kupg_dict[fid].geometry().buffer(0, 5).contains(geom)
                        for fid in index_kupg.intersects(geom.boundingBox())
                        if kupg_dict.get(fid)
                    )
                    if (not on_kupg and skrot != "gaz.") or (on_kupg and skrot == "gaz."):
                        obiektyZbledami.append(f)
        elif klasa == "OT_BUIT_P":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                istnienie = get_attr(f, "kategoriaIstnienia")
                info = get_attr(f, "informacjaDodatkowa")

                if rodzaj == "szyb naftowy lub gazowy" and istnienie == "eksploatowany":
                    if "gaz" in info:
                        if skrot != "gaz.":
                            obiektyZbledami.append(f)
                    elif skrot == "gaz.":
                        obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'gaz.': {e}")
        return []



def kontrolaSkrot_hod(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami, klasa = [], layer.name()[-9:]
        if klasa not in ['OT_KUPG_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kupg_layer, bubd_layer = layers.get("OT_KUPG_A"), layers.get("OT_BUBD_A")
        if not kupg_layer or not bubd_layer:
            return []
        index_kupg, kupg_dict = QgsSpatialIndex(), {}
        for f in kupg_layer.getFeatures():
            if get_attr(f, "rodzaj") == "gospodarstwo hodowlane" and f.geometry() and not f.geometry().isEmpty():
                index_kupg.insertFeature(f)
                kupg_dict[f.id()] = f
        index_bubd, bubd_dict = QgsSpatialIndex(), {}
        for f in bubd_layer.getFeatures():
            if get_attr(f, "przewazajacaFunkcjaBudynku") == "budynek produkcyjny zwierząt hodowlanych" and \
               get_attr(f, "kategoriaIstnienia") != "zniszczony" and f.geometry() and not f.geometry().isEmpty():
                index_bubd.insertFeature(f)
                bubd_dict[f.id()] = f
        if klasa == "OT_KUPG_A":
            for f in layer.getFeatures():
                rodzaj = get_attr(f, "rodzaj")
                skrot = get_attr(f, "skrotKartograficzny")
                geom = f.geometry()
                if not geom or rodzaj != "gospodarstwo hodowlane":
                    if skrot == "hod.":
                        obiektyZbledami.append(f)
                    continue
                found_budynek = any(
                    geom.buffer(0, 5).contains(bubd_dict[fid].geometry())
                    for fid in index_bubd.intersects(geom.boundingBox())
                    if bubd_dict.get(fid)
                )
                if found_budynek and skrot != "hod.":
                    obiektyZbledami.append(f)
                elif not found_budynek and skrot == "hod.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                skrot = get_attr(f, "skrotKartograficzny")
                geom = f.geometry()
                if not geom or funkcja != "budynek produkcyjny zwierząt hodowlanych" or istnienie == "zniszczony":
                    continue
                lezy_na_kupg = any(
                    kupg_dict[fid].geometry().buffer(0, 5).contains(geom)
                    for fid in index_kupg.intersects(geom.boundingBox())
                    if get_attr(kupg_dict[fid], "rodzaj") == "gospodarstwo hodowlane"
                )
                if not lezy_na_kupg and skrot != "hod.":
                    obiektyZbledami.append(f)
                elif lezy_na_kupg and skrot == "hod.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'hod.': {e}")
        return []


def kontrolaSkrot_kemp(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"       
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUHO_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuho_layer = layers.get("OT_KUHO_A")
        bubd_layer = layers.get("OT_BUBD_A")
        if not kuho_layer or not bubd_layer:
            return []
        index_kuho, kuho_dict = QgsSpatialIndex(), {}
        for f in kuho_layer.getFeatures():
            if get_attr(f, "rodzaj") == "kemping" and f.geometry() and not f.geometry().isEmpty():
                index_kuho.insertFeature(f)
                kuho_dict[f.id()] = f
        if klasa == "OT_KUHO_A":
            for f in layer.getFeatures():
                skrot, rodzaj = get_attr(f, "skrotKartograficzny"), get_attr(f, "rodzaj")
                if rodzaj == "kemping":
                    if skrot != "kemp.":
                        obiektyZbledami.append(f)
                elif skrot == "kemp.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or geom.isEmpty(): continue
                if funkcja == "domek kempingowy" and istnienie != "zniszczony":
                    on_kuho = any(
                        kuho_dict[fid].geometry().buffer(0, 5).contains(geom)
                        for fid in index_kuho.intersects(geom.boundingBox())
                        if kuho_dict.get(fid)
                    )
                    if (not on_kuho and skrot != "kemp.") or (on_kuho and skrot == "kemp."):
                        obiektyZbledami.append(f)
                elif skrot == "kemp.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'kemp.': {e}")
        return []



def kontrolaSkrot_lad(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami, klasa = [], layer.name()[-9:]
        if klasa not in ['OT_KUKO_A', 'OT_OIKM_P']: 
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuko_layer, oikm_layer = layers.get("OT_KUKO_A"), layers.get("OT_OIKM_P")
        if not kuko_layer or not oikm_layer: 
            return []
        index_kuko_valid, kuko_valid = QgsSpatialIndex(), {}
        for f in kuko_layer.getFeatures():
            if get_attr(f, "rodzaj") == "lotnisko lub lądowisko" and f.geometry() and not f.geometry().isEmpty():
                index_kuko_valid.insertFeature(f)
                kuko_valid[f.id()] = f
        if klasa == "OT_KUKO_A":
            for f in layer.getFeatures():
                skrot     = get_attr(f, "skrotKartograficzny")
                rodzaj    = get_attr(f, "rodzaj")
                info      = get_attr(f, "informacjaDodatkowa")
                geom      = f.geometry()
                warunek_poprawny = (
                    rodzaj == "lotnisko lub lądowisko" and
                    "lądowisk" in info and
                    geom and not geom.isEmpty()
                )
                if warunek_poprawny != (skrot == "ląd."):
                    obiektyZbledami.append(f)
        elif klasa == "OT_OIKM_P":
            for f in layer.getFeatures():
                skrot  = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                geom   = f.geometry()
                if rodzaj != "lądowisko dla helikopterów" or not geom:
                    if skrot == "ląd.":
                        obiektyZbledami.append(f)
                    continue
                on_kuko = any(
                    kuko_valid[fid].geometry().contains(geom)
                    for fid in index_kuko_valid.intersects(geom.boundingBox())
                    if fid in kuko_valid
                )
                if (on_kuko and skrot == "ląd.") or (not on_kuko and skrot != "ląd."):
                    obiektyZbledami.append(f)
        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'ląd.': {e}")
        return []



def kontrolaSkrot_letn(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUSK_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kusk_layer = layers.get("OT_KUSK_A")
        if not kusk_layer:
            return []
        index_kusk, kusk_dict = QgsSpatialIndex(), {}
        for f in kusk_layer.getFeatures():
            if get_attr(f, "rodzaj") == "zespół domów letniskowych" and f.geometry() and not f.geometry().isEmpty():
                index_kusk.insertFeature(f)
                kusk_dict[f.id()] = f
        if klasa == "OT_KUSK_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if rodzaj == "zespół domów letniskowych":
                    if skrot != "letn.":
                        obiektyZbledami.append(f)
                elif skrot == "letn.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or geom.isEmpty(): continue
                if funkcja == "dom letniskowy" and istnienie != "zniszczony":
                    on_kusk = any(
                        kusk_dict[fid].geometry().buffer(0, 5).intersects(geom)
                        for fid in index_kusk.intersects(geom.boundingBox())
                        if kusk_dict.get(fid)
                    )
                    if (not on_kusk and skrot != "letn.") or (on_kusk and skrot == "letn."):
                        obiektyZbledami.append(f)
                elif skrot == "letn.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'letn.': {e}")
        return []


def kontrolaSkrot_osrWyp(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUHO_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuho_layer = layers.get("OT_KUHO_A")
        if not kuho_layer:
            return []
        index_kuho, kuho_dict = QgsSpatialIndex(), {}
        for f in kuho_layer.getFeatures():
            if get_attr(f, "rodzaj") == "ośrodek wypoczynkowy" and f.geometry() and not f.geometry().isEmpty():
                index_kuho.insertFeature(f)
                kuho_dict[f.id()] = f
        if klasa == "OT_KUHO_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if rodzaj == "ośrodek wypoczynkowy":
                    if skrot != "ośr. wyp.":
                        obiektyZbledami.append(f)
                elif skrot == "ośr. wyp.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or geom.isEmpty(): continue
                if funkcja == "ośrodek szkoleniowo-wypoczynkowy" and istnienie != "zniszczony":
                    on_kuho = any(
                        kuho_dict[fid].geometry().buffer(0, 5).contains(geom)
                        for fid in index_kuho.intersects(geom.boundingBox())
                        if kuho_dict.get(fid)
                    )
                    if (not on_kuho and skrot != "ośr. wyp.") or (on_kuho and skrot == "ośr. wyp."):
                        obiektyZbledami.append(f)
                elif skrot == "ośr. wyp.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'ośr. wyp.': {e}")
        return []


def kontrolaSkrot_parking(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUKO_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuko_layer = layers.get("OT_KUKO_A")
        if not kuko_layer:
            return []
        index_kuko, kuko_dict = QgsSpatialIndex(), {}
        for f in kuko_layer.getFeatures():
            if get_attr(f, "rodzaj") == "parking" and f.geometry() and not f.geometry().isEmpty():
                index_kuko.insertFeature(f)
                kuko_dict[f.id()] = f
        if klasa == "OT_KUKO_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if rodzaj == "parking":
                    if skrot != "p.":
                        obiektyZbledami.append(f)
                elif skrot == "p.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                geom = f.geometry()
                if not geom or geom.isEmpty(): continue
                if funkcja == "parking wielopoziomowy" and istnienie != "zniszczony":
                    on_kuko = any(
                        kuko_dict[fid].geometry().buffer(0, 5).contains(geom)
                        for fid in index_kuko.intersects(geom.boundingBox())
                        if kuko_dict.get(fid)
                    )
                    if (not on_kuko and skrot != "p.") or (on_kuko and skrot == "p."):
                        obiektyZbledami.append(f)
                elif skrot == "p.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'p.': {e}")
        return []


def kontrolaSkrot_pKol(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        if layer.name()[-9:] != "OT_OIKM_P": return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuko_layer = layers.get("OT_KUKO_A")
        if not kuko_layer: return []
        index_kuko, kuko_dict = QgsSpatialIndex(), {}
        for f in kuko_layer.getFeatures():
            if get_attr(f, "rodzaj") == "stacja kolejowa" and f.geometry() and not f.geometry().isEmpty():
                index_kuko.insertFeature(f)
                kuko_dict[f.id()] = f
        for f in layer.getFeatures():
            geom, skrot = f.geometry(), get_attr(f, "skrotKartograficzny")
            if not geom or get_attr(f, "rodzaj") != "stacja lub przystanek kolejowy" or get_attr(f, "kategoriaIstnienia") in ["zniszczony", "nieczynny"]:
                if skrot == "p. kol.": obiektyZbledami.append(f)
                continue
            on_kuko = any(kuko_dict[fid].geometry().contains(geom) for fid in index_kuko.intersects(geom.boundingBox()) if fid in kuko_dict)
            if (not on_kuko and skrot != "p. kol.") or (on_kuko and skrot == "p. kol."): obiektyZbledami.append(f)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w kontroli skrótu 'p. kol.': {e}")
        return []

def kontrolaSkrot_port(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami, klasa = [], layer.name()[-9:]
        if klasa not in ['OT_KUKO_A', 'OT_BUBD_A']: return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuko_layer, bubd_layer = layers.get("OT_KUKO_A"), layers.get("OT_BUBD_A")
        if not kuko_layer or not bubd_layer: return []
        index_kuko, kuko_dict = QgsSpatialIndex(), {}
        for f in kuko_layer.getFeatures():
            if get_attr(f, "rodzaj") == "port wodny lub przystań" and f.geometry() and not f.geometry().isEmpty():
                index_kuko.insertFeature(f)
                kuko_dict[f.id()] = f
        if klasa == "OT_KUKO_A":
            for f in layer.getFeatures():
                geom, skrot, rodzaj = f.geometry(), get_attr(f, "skrotKartograficzny"), get_attr(f, "rodzaj")
                if not geom or rodzaj != "port wodny lub przystań":
                    if skrot == "port":
                        obiektyZbledami.append(f)
                    continue
                if skrot != "port":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                geom = f.geometry()
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                if not geom or funkcja != "terminal portowy" or istnienie == "zniszczony":
                    if skrot == "port":
                        obiektyZbledami.append(f)
                    continue
                lezy_na_kuko = any(
                    kuko_dict[fid].geometry().buffer(0, 5).contains(geom)
                    for fid in index_kuko.intersects(geom.boundingBox())
                    if get_attr(kuko_dict[fid], "rodzaj") == "port wodny lub przystań"
                )
                if not lezy_na_kuko and skrot != "port":
                    obiektyZbledami.append(f)
                elif lezy_na_kuko and skrot == "port":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'port': {e}")
        return []


def kontrolaSkrot_pomp(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami, klasa = [], layer.name()[-9:]
        if klasa not in ['OT_KUPG_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kupg_layer, bubd_layer = layers.get("OT_KUPG_A"), layers.get("OT_BUBD_A")
        if not kupg_layer or not bubd_layer:
            return []
        index_kupg, kupg_dict = QgsSpatialIndex(), {}
        rodzaje_kompleksow = {
            "przepompownia",
            "zakład wodociągowy",
            "zakład utylizacji",
            "oczyszczalnia ścieków",
            "rafineria",
            "kopalnia",
            "gazownia",
            "elektrownia",
            "elektrociepłownia",
            "gospodarstwo hodowlane"
        }
        for f in kupg_layer.getFeatures():
            rodzaj = get_attr(f, "rodzaj")
            skrot = get_attr(f, "skrotKartograficzny")
            if rodzaj in rodzaje_kompleksow and f.geometry() and not f.geometry().isEmpty():
                if rodzaj != "gospodarstwo hodowlane" or skrot == "hod.":
                    index_kupg.insertFeature(f)
                    kupg_dict[f.id()] = f
        if klasa == "OT_KUPG_A":
            for f in layer.getFeatures():
                geom = f.geometry()
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if not geom or rodzaj != "przepompownia":
                    if skrot == "pomp.":
                        obiektyZbledami.append(f)
                    continue
                if skrot != "pomp.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                geom = f.geometry()
                skrot = get_attr(f, "skrotKartograficzny")
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                if not geom or funkcja != "stacja pomp" or istnienie == "zniszczony":
                    if skrot == "pomp.":
                        obiektyZbledami.append(f)
                    continue
                lezy_na_kompleksie = any(
                    kupg_dict[fid].geometry().buffer(0, 5).contains(geom)
                    for fid in index_kupg.intersects(geom.boundingBox())
                )
                if not lezy_na_kompleksie and skrot != "pomp.":
                    obiektyZbledami.append(f)
                elif lezy_na_kompleksie and skrot == "pomp.":
                    obiektyZbledami.append(f)
        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'pomp.': {e}")
        return []



def kontrolaSkrot_rafin(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUPG_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kupg_layer = layers.get("OT_KUPG_A")
        if not kupg_layer:
            return []
        index_kupg, kupg_dict = QgsSpatialIndex(), {}
        for f in kupg_layer.getFeatures():
            if get_attr(f, "rodzaj") == "rafineria" and f.geometry() and not f.geometry().isEmpty():
                index_kupg.insertFeature(f)
                kupg_dict[f.id()] = f
        if klasa == "OT_KUPG_A":
            for f in layer.getFeatures():
                rodzaj = get_attr(f, "rodzaj")
                skrot = get_attr(f, "skrotKartograficzny")
                if rodzaj == "rafineria":
                    if skrot != "rafin.":
                        obiektyZbledami.append(f)
                elif skrot == "rafin.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                skrot = get_attr(f, "skrotKartograficzny")
                geom = f.geometry()
                if not geom or geom.isEmpty():
                    continue
                if funkcja == "rafineria" and istnienie != "zniszczony":
                    on_kupg = any(
                        kupg_dict[fid].geometry().buffer(0, 5).contains(geom)
                        for fid in index_kupg.intersects(geom.boundingBox())
                        if fid in kupg_dict
                    )
                    if (not on_kupg and skrot == "rafin.") or (on_kupg and skrot != "rafin."):
                        obiektyZbledami.append(f)
                elif skrot == "rafin.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'rafin.': {e}")
        return []



def kontrolaSkrot_rdst(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami, klasa = [], layer.name()[-9:]
        if klasa not in ['OT_BUBD_A', 'OT_BUWT_P']: return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        bubd_layer, buwt_layer = layers.get("OT_BUBD_A"), layers.get("OT_BUWT_P")
        if not bubd_layer or not buwt_layer: return []
        index_bubd, bubd_dict = QgsSpatialIndex(), {}
        for f in bubd_layer.getFeatures():
            if get_attr(f, "przewazajacaFunkcjaBudynku") in ["centrum telekomunikacyjne", "stacja nadawcza radia i telewizji"] and get_attr(f, "kategoriaIstnienia") != "zniszczony" and f.geometry() and not f.geometry().isEmpty():
                index_bubd.insertFeature(f)
                bubd_dict[f.id()] = f
        if klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                skrot, funkcja, istnienie = get_attr(f, "skrotKartograficzny"), get_attr(f, "przewazajacaFunkcjaBudynku"), get_attr(f, "kategoriaIstnienia")
                if funkcja in ["centrum telekomunikacyjne", "stacja nadawcza radia i telewizji"] and istnienie != "zniszczony":
                    if skrot != "rdst.": obiektyZbledami.append(f)
                elif skrot == "rdst.": obiektyZbledami.append(f)
        elif klasa == "OT_BUWT_P":
            for f in layer.getFeatures():
                skrot, rodzaj, istnienie, geom = get_attr(f, "skrotKartograficzny"), get_attr(f, "rodzaj"), get_attr(f, "kategoriaIstnienia"), f.geometry()
                if rodzaj != "maszt lub wieża telekomunikacyjna" or istnienie == "zniszczony" or not geom:
                    if skrot == "rdst.": obiektyZbledami.append(f)
                    continue
                on_bubd = any(bubd_dict[fid].geometry().contains(geom) for fid in index_bubd.intersects(geom.boundingBox()) if fid in bubd_dict)
                if (not on_bubd and skrot != "rdst.") or (on_bubd and skrot == "rdst."): obiektyZbledami.append(f)
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w kontroli skrótu 'rdst.': {e}")
        return []


def kontrolaSkrot_st(layer):
    try:
        def get_attr(f, attr):
            return str(f[attr] or "").strip().lower() if attr in f.fields().names() else "__MISSING__"
        obiektyZbledami = []
        klasa = layer.name()[-9:]
        if klasa not in ['OT_KUKO_A', 'OT_BUBD_A']:
            return []
        layers = {l.name()[-9:]: l for l in QgsProject.instance().mapLayers().values()}
        kuko_layer = layers.get("OT_KUKO_A")
        if not kuko_layer:
            return []
        index_kuko, kuko_dict = QgsSpatialIndex(), {}
        for f in kuko_layer.getFeatures():
            if get_attr(f, "rodzaj") == "stacja kolejowa" and f.geometry() and not f.geometry().isEmpty():
                index_kuko.insertFeature(f)
                kuko_dict[f.id()] = f
        if klasa == "OT_KUKO_A":
            for f in layer.getFeatures():
                skrot = get_attr(f, "skrotKartograficzny")
                rodzaj = get_attr(f, "rodzaj")
                if rodzaj == "stacja kolejowa":
                    if skrot != "st.":
                        obiektyZbledami.append(f)
                elif skrot == "st.":
                    obiektyZbledami.append(f)
        elif klasa == "OT_BUBD_A":
            for f in layer.getFeatures():
                funkcja = get_attr(f, "przewazajacaFunkcjaBudynku")
                istnienie = get_attr(f, "kategoriaIstnienia")
                skrot = get_attr(f, "skrotKartograficzny")
                geom = f.geometry()

                if not geom or geom.isEmpty():
                    continue
                if funkcja == "dworzec kolejowy" and istnienie != "zniszczony":
                    on_kuko = any(
                        kuko_dict[fid].geometry().buffer(0.5, 5).contains(geom)
                        for fid in index_kuko.intersects(geom.boundingBox())
                        if fid in kuko_dict
                    )
                    if not on_kuko:
                        if skrot != "st.":
                            obiektyZbledami.append(f)
                    else:
                        if skrot == "st.":
                            obiektyZbledami.append(f)
                elif skrot == "st.":
                    obiektyZbledami.append(f)

        return obiektyZbledami

    except Exception as e:
        print(f"Błąd w kontroli skrótu 'st.': {e}")
        return []

    
    
def kontrolaUzyciaSkrotu(layer):
    def get_attr(f, attr):
        return str(f[attr]).strip() if attr in f.fields().names() and f[attr] not in [None, 'NULL'] else ""
    obiektyZbledami = []
    id_zbledami = set()
    try:
        klasa = layer.name()[-9:]
        lista_dozwolonych = {
            "OT_BUBD_A": ['amb.', 'B', 'bas. kąp.', 'biur.', 'bud.', 'c. han.', 'd. dz.', 'd. h.', 'd. k.', 'd. op.', 'd. paraf.', 'd. s.', 'd. wes.', 'd. wych.', 'd. wyp.', 'el.', 'elc.', 'elw.', 'gar.', 'gaz.', 'hod.', 'H', 'h. sport.', 'h. targ.', 'int.', 'K', 'kemp.', 'kl.', 'kort', 'letn.', 'lotn.', 'M', 'mag.', 'nadl.', 'obs. astr.', 'ośr. wyp.', 'P', 'p.', 'pleb.', 'pomp.', 'port', 'port lot.', 'pocz.', 'pog. rat.', 'przedszk.', 'rafin.', 'rem.', 'rest.', 'rdst.', 'S', 'san.', 'schr.', 'sil.', 'SP', 'st.', 'szk.', 'szpit.', 'T', 'UG', 'UM', 'UMG', 'UMr', 'UW', 'zaj.', 'zdr.', 'z. kar.', 'żłb.'],
            "OT_KUKO_A": ['b. tran.', 'dw. aut.', 'ląd.', 'lotn.', 'p.', 'port', 'st.', 'zaj.'],
            "OT_BUSP_A": ['bas. kąp.', 'kort', 'p. golf.', 'pl. sport.'],
            "OT_KUPG_A": ['el.', 'elc.', 'gaz.', 'hod.', 'kop.', 'metalurg.', 'oczyszcz.', 'pomp.', 'rafin.', 'utyliz.', 'wdc.'],
            "OT_KUZA_A": ['fort.'],
            "OT_PTWZ_A": ['gl.', 'hłd.', 'kłm', 'piask.', 'żw.'],
            "OT_KUHO_A": ['kemp.', 'ośr. wyp.'],
            "OT_KUSK_A": ['letn.', 'stadn.'],
            "OT_OIKM_P": ['ląd.', 'niecz.', 'p. kol.'],
            "OT_SKPP_L": ['b.', 'pr.', 'pw.'],
            "OT_BUIT_P": ['gaz.', 'nft.'],
            "OT_BUWT_P": ['niecz.', 'rdst.', 'w. ciśn.', 'w. obs.'],
            "OT_BUZT_A": ['osad.'],
            "OT_BUIB_A": ['rmp.'],
            "OT_BUIB_L": ['rmp.'],
            "OT_OIOR_A": ['r. zab.'],
            "OT_SKTR_L": ['niecz.'],
            "OT_OIPR_P": ['źr. min.'],
            "OT_PTPL_A": ['bud.'],
            "OT_KUHU_A": ['targ.']
        }
        dozwolone = lista_dozwolonych.get(klasa)
        layer.startEditing()
        for f in layer.getFeatures():
            skrot = get_attr(f, 'skrotKartograficzny')
            gml_id = get_attr(f, 'gml_id')
            if skrot:
                if (dozwolone is not None and skrot not in dozwolone) or (dozwolone is None):
                    if gml_id not in id_zbledami:
                        komunikat = f"NIEDOZWOLONY skrotKartograficzny: {skrot}"
                        nowy_gml_id = (gml_id + " | " if gml_id else "") + komunikat
                        f.setAttribute(layer.fields().indexFromName("gml_id"), nowy_gml_id)
                        layer.updateFeature(f)
                        obiektyZbledami.append(f)
                        id_zbledami.add(gml_id)

        layer.commitChanges()

    except Exception as e:
        print(f"Błąd podczas kontroli użycia dozwolonych skrótów: {e}")

    return obiektyZbledami


def kontrolaUzyciaKodu(layer):
    """
    Kontrola użycia kodu kartograficznego w odpowiedniej klasie.
    Wykrywa nadmiarowe przypisanie kodu w warstwach nieuprawnionych
    do posiadania tego atrybutu oraz niezgodności z listą dozwolonych wystąpień.
    """
    def get_attr(f, attr):
        return str(f[attr]).strip() if attr in f.fields().names() and f[attr] not in [None, 'NULL'] else ""
    obiektyZbledami, id_zbledami = [], set()
    try:
        klasa = layer.name()[-9:]
        lista_dozwolonych = {
                "OT_ADJA_A": ['0010_501', '0010_503', '0010_504', '0010_505', '0010_506'],
                "OT_BUBD_A": ['0010_317_1', '0010_318_1', '0010_319', '0010_320_1', '0010_321', '0010_323_1', '0010_324', '0010_325_1', '0010_326_1', '0010_327_1', '0010_327_2', '0010_328_1', '0010_328_2', '0010_329_1', '0010_329_2', '0010_337', '0010_630'],
                "OT_BUHD_A": ['0010_616', '0010_617_1'],
                "OT_BUHD_L": ['0010_616', '0010_620'],
                "OT_BUIB_A": ['0010_226', '0010_350', '0010_351_1'],
                "OT_BUIB_L": ['0010_226', '0010_351_2'],
                "OT_BUIN_L": ['0010_131_1', '0010_131_2', '0010_133_1', '0010_133_2', '0010_134_1', '0010_134_2', '0010_136', '0010_151_1', '0010_219_1', '0010_219_2', '0010_220_1', '0010_220_2', '0010_638_1', '0010_638_2'],
                "OT_BUIT_A": ['0010_413', '0010_421_1'],
                "OT_BUIT_P": ['0010_412', '0010_413', '0010_422', '0010_427', '0010_447', '0010_448'],
                "OT_BUSP_A": ['0010_338', '0010_341_1', '0010_611'],
                "OT_BUSP_L": ['0010_339'],
                "OT_BUTR_L": ['0010_227', '0010_228', '0010_229', '0010_433_1', '0010_433_2', '0010_434'],
                "OT_BUTR_P": ['0010_215'],
                "OT_BUUO_L": ['0010_349', '0010_626', '0010_627', '0010_628'],
                "OT_BUWT_A": ['0010_324'],
                "OT_BUWT_P": ['0010_334', '0010_411', '0010_424', '0010_425', '0010_426', '0010_428'],
                "OT_BUZM_L": ['0010_808', '0010_809_1', '0010_809_2', '0010_810_1', '0010_810_2', '0010_811_1', '0010_811_2'],
                "OT_BUZT_A": ['0010_414_1', '0010_431_1', '0010_431_2'],
                "OT_BUZT_P": ['0010_414_2'],
                "OT_KUPG_A": ['0010_401'],
                "OT_KUKO_P": ['0010_142'],
                "OT_KUSC_A": ['0010_343_1', '0010_344_1', '0010_345_1', '0010_346_1', '0010_347_1'],
                "OT_KUPW_A": ['0010_510'],
                "OT_OIKM_L": ['0010_145'],
                "OT_OIKM_P": ['0010_143', '0010_225', '0010_631'],
                "OT_OIMK_A": ['0010_725', '0010_726'],
                "OT_OIOR_A": ['0010_325_1', '0010_326_1', '0010_335', '0010_336', '0010_337'],
                "OT_OIOR_L": ['0010_348', '0010_629_1', '0010_629_2'],
                "OT_OIOR_P": ['0010_330', '0010_331', '0010_332', '0010_333', '0010_335', '0010_613'],
                "OT_OIPR_L": ['0010_614', '0010_615', '0010_711', '0010_729', '0010_733'],
                "OT_OIPR_P": ['0010_612', '0010_728', '0010_731', '0010_732', '0010_734', '0010_815', '0010_816', '0010_817'],
                "OT_OISZ_A": ['0010_727'],
                "OT_PTGN_A": ['0010_736', '0010_737', '0010_738'],
                "OT_PTKM_A": ['0010_140'],
                "OT_PTLZ_A": ['0010_703', '0010_704', '0010_705', '0010_706', '0010_708', '0010_709', '0010_710'],
                "OT_PTPL_A": ['0010_140'],
                "OT_PTRK_A": ['0010_713', '0010_714'],
                "OT_PTSO_A": ['0010_430'],
                "OT_PTTR_A": ['0010_723'],
                "OT_PTUT_A": ['0010_718', '0010_719', '0010_720'],
                "OT_PTWP_A": ['0010_601'],
                "OT_PTWZ_A": ['0010_430', '0010_736', '0010_737', '0010_738'],
                "OT_PTZB_A": ['0010_140', '0010_706', '0010_723'],
                "OT_RTLW_L": ['0010_802', '0010_803', '0010_804', '0010_805', '0010_807_1', '0010_807_2', '0010_814'],
                "OT_RTPW_P": ['0010_812', '0010_813', '0010_819'],
                "OT_SKDR_L": ['0010_103', '0010_108'],
                "OT_SKJZ_L": ['0010_102', '0010_107', '0010_116_1', '0010_116_2', '0010_120_1', '0010_120_2', '0010_122_1', '0010_122_2', '0010_124', '0010_126', '0010_127'],
                "OT_SKPP_L": ['0010_137'],
                "OT_SKRP_L": ['0010_128_1', '0010_128_2', '0010_129'],
                "OT_SKTR_L": ['0010_202', '0010_203', '0010_204', '0010_206', '0010_207', '0010_208', '0010_209', '0010_211', '0010_213', '0010_217', '0010_218'],
                "OT_SULN_L": ['0010_444', '0010_446'],
                "OT_SUPR_L": ['0010_437', '0010_438', '0010_440', '0010_441', '0010_442', '0010_443'],
                "OT_SWKN_L": ['0010_606'],
                "OT_SWRM_L": ['0010_606'],
                "OT_SWRS_L": ['0010_606'],
                "OT_TCPN_A": ['0010_507'],
                "OT_TCPK_A": ['0010_509'],
                "OT_TCRZ_A": ['0010_508_1']
        }
        dozwolone = lista_dozwolonych.get(klasa)
        layer.startEditing()
        for f in layer.getFeatures():
            kod = get_attr(f, 'kodKarto10k')
            gml_id = get_attr(f, 'gml_id')
            if 'kodKarto250k' in f.fields().names():
                if gml_id not in id_zbledami:
                    komunikat = f"NIEDOZWOLONY atrybut kodKarto250k"
                    nowy_gml_id = (gml_id + " | " if gml_id else "") + komunikat
                    f.setAttribute(layer.fields().indexFromName("gml_id"), nowy_gml_id)
                    layer.updateFeature(f)
                    obiektyZbledami.append(f)
                    id_zbledami.add(gml_id)
            if kod and ((dozwolone is not None and kod not in dozwolone) or (dozwolone is None)):
                if gml_id not in id_zbledami:
                    komunikat = f"NIEDOZWOLONY kodKarto10k: {kod}"
                    nowy_gml_id = (gml_id + " | " if gml_id else "") + komunikat
                    f.setAttribute(layer.fields().indexFromName("gml_id"), nowy_gml_id)
                    layer.updateFeature(f)
                    obiektyZbledami.append(f)
                    id_zbledami.add(gml_id)
        layer.commitChanges()
    except Exception as e:
        print(f"Błąd podczas kontroli użycia kodu kartograficznego: {e}")
    return obiektyZbledami


def kodKarto10kNULL(layer):
    obiektyZbledami = []
    try:
        oimka = None
        ptsoa = None
        ptwza = None
        ptgna = None
        if layer.name().__contains__('RTLW_L'):
            oimka=layer.name().replace("RTLW_L","OIMK_A") # Bagno
            if QgsProject().instance().mapLayersByName(oimka):
               oimka = QgsProject().instance().mapLayersByName(oimka)[0]
            ptsoa=layer.name().replace("RTLW_L","PTSO_A")[0] # teren z odpadami komunalnymi i przemysłowymi
            if QgsProject().instance().mapLayersByName(ptsoa):
                ptsoa = QgsProject().instance().mapLayersByName(ptsoa)[0]
            ptwza = layer.name().replace("RTLW_L","PTWZ_A")[0] # zwałowisko, wyrobisko
            if QgsProject().instance().mapLayersByName(ptwza):
                ptwza = QgsProject().instance().mapLayersByName(ptwza)[0]
            ptgna=layer.name().replace("RTLW_L","PTGN_A")
            if  QgsProject().instance().mapLayersByName(ptgna):
                QgsProject().instance().mapLayersByName(ptgna)[0] # teren piaszczysty, żwirowy, kamienisty i rumowisko  skalne
            
        extractbyexpression = processing.run("native:extractbyexpression", {
            'INPUT': layer,
            'EXPRESSION': '"rodzaj" in (\'poziomica\') and "kodKarto10k" != \'\'',
            'FAIL_OUTPUT': 'memory:',
            'OUTPUT': 'memory:'
        })
        if isinstance(oimka, QgsVectorLayer): 
            # bufor -0.04 m
            bufor = processing.run("native:buffer", {
                'INPUT': oimka,
                'DISTANCE': -0.04,
                'SEGMENTS': 10,
                'DISSOLVE': False,
                'OUTPUT': 'memory:'
            })
            pojedynczeOIMKA = processing.run("native:multiparttosingleparts", {
                'INPUT': bufor['OUTPUT'],
                'OUTPUT': 'memory:'
            })
            extractBagno = processing.run("native:extractbyexpression", {
                'INPUT': pojedynczeOIMKA['OUTPUT'],
                'EXPRESSION': '"rodzaj" in (\'bagno\')',
                'FAIL_OUTPUT': 'memory:',
                'OUTPUT': 'memory:'
            })
            extractbylocation = processing.run("native:extractbylocation", {
                'INPUT': extractbyexpression['OUTPUT'],
                'INTERSECT': extractBagno['OUTPUT'],
                'PREDICATE': [0],
                'OUTPUT': 'memory:'
            })
            if extractbylocation['OUTPUT'].featureCount() > 0:
                for obj in extractbylocation['OUTPUT'].getFeatures():
                    obiektyZbledami.append(obj)
        if isinstance(ptgna, QgsVectorLayer):
            # bufor -0.04 m
            bufor = processing.run("native:buffer", {
                'INPUT': ptgna,
                'DISTANCE': -0.04,
                'SEGMENTS': 10,
                'DISSOLVE': False,
                'OUTPUT': 'memory:'
            })
            pojedynczePTGNA = processing.run("native:multiparttosingleparts", {
                'INPUT': bufor['OUTPUT'],
                'OUTPUT': 'memory:'
            })
            extractGrunt = processing.run("native:extractbyexpression", {
                'INPUT': pojedynczePTGNA['OUTPUT'],
                'EXPRESSION': '"rodzaj" in (\'piarg, usypisko lub rumowisko skalne\', \'teren kamienisty\', \'teren piaszczysty lub żwirowy\')',
                'FAIL_OUTPUT': 'memory:',
                'OUTPUT': 'memory:'
            })
            extractbylocation = processing.run("native:extractbylocation", {
                'INPUT': extractbyexpression['OUTPUT'],
                'INTERSECT': extractGrunt['OUTPUT'],
                'PREDICATE': [0],
                'OUTPUT': 'memory:'
            })
            if extractbylocation['OUTPUT'].featureCount() > 0:
                for obj in extractbylocation['OUTPUT'].getFeatures():
                    obiektyZbledami.append(obj)
        if isinstance(ptsoa, QgsVectorLayer):
              # bufor -0.04 m
              bufor = processing.run("native:buffer", {
                  'INPUT': ptsoa,
                  'DISTANCE': -0.04,
                  'SEGMENTS': 10,
                  'DISSOLVE': False,
                  'OUTPUT': 'memory:'
              })
              pojedynczePTSOA = processing.run("native:multiparttosingleparts", {
                  'INPUT': bufor['OUTPUT'],
                  'OUTPUT': 'memory:'
              })
              extractbylocation = processing.run("native:extractbylocation", {
                  'INPUT': extractbyexpression['OUTPUT'],
                  'INTERSECT': pojedynczePTSOA['OUTPUT'],
                  'PREDICATE': [0],
                  'OUTPUT': 'memory:'
              })
              if extractbylocation['OUTPUT'].featureCount() > 0:
                  for obj in extractbylocation['OUTPUT'].getFeatures():
                      obiektyZbledami.append(obj)
        if isinstance(ptwza, QgsVectorLayer):
              # bufor -0.04 m
              bufor = processing.run("native:buffer", {
                  'INPUT': ptwza,
                  'DISTANCE': -0.04,
                  'SEGMENTS': 10,
                  'DISSOLVE': False,
                  'OUTPUT': 'memory:'
              })
              pojedynczePTWZA = processing.run("native:multiparttosingleparts", {
                  'INPUT': bufor['OUTPUT'],
                  'OUTPUT': 'memory:'
              })
              extractbylocation = processing.run("native:extractbylocation", {
                  'INPUT': extractbyexpression['OUTPUT'],
                  'INTERSECT': pojedynczePTWZA['OUTPUT'],
                  'PREDICATE': [0],
                  'OUTPUT': 'memory:'
              })
              if extractbylocation['OUTPUT'].featureCount() > 0:
                  for obj in extractbylocation['OUTPUT'].getFeatures():
                      obiektyZbledami.append(obj)
    except Exception as e:
        print ("błąd w funkcji kodKarto10kNULL: ", e)
    return obiektyZbledami


def blednePolozeniePktWys(layer):
    obiektyZbledami = []
    try:
        bubda = None
        buzta = None
        oiora = None
        buwta = None
        ptnza = None
        ptsoa = None
        ptwpa = None
        ptwza = None
        if layer.name().__contains__('RTPW_P'):
            bubda=layer.name().replace("RTPW_P","BUBD_A")
            if QgsProject().instance().mapLayersByName(bubda):
               bubda= QgsProject().instance().mapLayersByName(bubda)[0]
            buwta=layer.name().replace("RTPW_P","BUWT_A")
            if QgsProject().instance().mapLayersByName(buwta):
                buwta=QgsProject().instance().mapLayersByName(buwta)[0]
            buzta=layer.name().replace("RTPW_P","BUZT_A")
            if QgsProject().instance().mapLayersByName(buzta):
                buzta=QgsProject().instance().mapLayersByName(buzta)[0]
            oiora=layer.name().replace("RTPW_P","OIOR_A")
            if QgsProject().instance().mapLayersByName(oiora):
                oiora=QgsProject().instance().mapLayersByName(oiora)[0]
            ptnza=layer.name().replace("RTPW_P","PTNZ_A")
            if QgsProject().instance().mapLayersByName(ptnza):
                ptnza=QgsProject().instance().mapLayersByName(ptnza)[0]
            ptsoa=layer.name().replace("RTPW_P","PTSO_A")
            if QgsProject().instance().mapLayersByName(ptsoa):
                ptsoa=QgsProject().instance().mapLayersByName(ptsoa)[0]
            ptwpa=layer.name().replace("RTPW_P","PTWP_A")
            if QgsProject().instance().mapLayersByName(ptwpa): 
               ptwpa= QgsProject().instance().mapLayersByName(ptwpa)[0]
            ptwza=layer.name().replace("RTPW_P","PTWZ_A")
            if QgsProject().instance().mapLayersByName(ptwza): 
                ptwza=QgsProject().instance().mapLayersByName(ptwza)[0]
            if isinstance(bubda, QgsVectorLayer):
                # bufor -0.02 m
                bufor = processing.run("native:buffer", {
                    'INPUT': bubda,
                    'DISTANCE': -0.02,
                    'SEGMENTS': 10,
                    'DISSOLVE': False,
                    'OUTPUT': 'memory:'
                })
                pojedynczeBUBDA = processing.run("native:multiparttosingleparts", {
                    'INPUT': bufor['OUTPUT'],
                    'OUTPUT': 'memory:'
                })
                extractbylocation = processing.run("native:extractbylocation", {
                    'INPUT': layer,
                    'INTERSECT': pojedynczeBUBDA['OUTPUT'],
                    'PREDICATE': [0],
                    'OUTPUT': 'memory:'
                })
                if extractbylocation['OUTPUT'].featureCount() > 0:
                    for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
            if isinstance(buwta, QgsVectorLayer):
                # bufor -0.02 m
                bufor = processing.run("native:buffer", {
                    'INPUT': buwta,
                    'DISTANCE': -0.02,
                    'SEGMENTS': 10,
                    'DISSOLVE': False,
                    'OUTPUT': 'memory:'
                })
                pojedynczeBUBDA = processing.run("native:multiparttosingleparts", {
                    'INPUT': bufor['OUTPUT'],
                    'OUTPUT': 'memory:'
                })
                extractbylocation = processing.run("native:extractbylocation", {
                    'INPUT': layer,
                    'INTERSECT': pojedynczeBUBDA['OUTPUT'],
                    'PREDICATE': [0],
                    'OUTPUT': 'memory:'
                })
                if extractbylocation['OUTPUT'].featureCount() > 0:
                    for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
            if isinstance(buzta, QgsVectorLayer):
                # bufor -0.02 m
                bufor = processing.run("native:buffer", {
                    'INPUT': buzta,
                    'DISTANCE': -0.02,
                    'SEGMENTS': 10,
                    'DISSOLVE': False,
                    'OUTPUT': 'memory:'
                })
                pojedynczeBUZTA = processing.run("native:multiparttosingleparts", {
                    'INPUT': bufor['OUTPUT'],
                    'OUTPUT': 'memory:'
                })
                extractbylocation = processing.run("native:extractbylocation", {
                    'INPUT': layer,
                    'INTERSECT': pojedynczeBUZTA['OUTPUT'],
                    'PREDICATE': [0],
                    'OUTPUT': 'memory:'
                })
                if extractbylocation['OUTPUT'].featureCount() > 0:
                    for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
            if isinstance(oiora, QgsVectorLayer):
                 # bufor -0.02 m
                 bufor = processing.run("native:buffer", {
                     'INPUT': oiora,
                     'DISTANCE': -0.02,
                     'SEGMENTS': 10,
                     'DISSOLVE': False,
                     'OUTPUT': 'memory:'
                 })
                 pojedynczeOIORA = processing.run("native:multiparttosingleparts", {
                     'INPUT': bufor['OUTPUT'],
                     'OUTPUT': 'memory:'
                 })
                 extractWiata = processing.run("native:extractbyexpression", {
                     'INPUT': pojedynczeOIORA['OUTPUT'],
                     'EXPRESSION': '"rodzaj" in (\'wiata lub altana\')',
                     'FAIL_OUTPUT': 'memory:',
                     'OUTPUT': 'memory:'
                 })
                 extractbylocation = processing.run("native:extractbylocation", {
                     'INPUT': layer, 
                     'INTERSECT': extractWiata['OUTPUT'],
                     'PREDICATE': [0],
                     'OUTPUT': 'memory:'
                 })
                 if extractbylocation['OUTPUT'].featureCount() > 0:
                     for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
            if isinstance(ptnza, QgsVectorLayer):
                # bufor -0.02 m
                bufor = processing.run("native:buffer", {
                    'INPUT': ptnza,
                    'DISTANCE': -0.02,
                    'SEGMENTS': 10,
                    'DISSOLVE': False,
                    'OUTPUT': 'memory:'
                })
                pojedynczePTNZA = processing.run("native:multiparttosingleparts", {
                    'INPUT': bufor['OUTPUT'],
                    'OUTPUT': 'memory:'
                })
                extractbylocation = processing.run("native:extractbylocation", {
                    'INPUT': layer,
                    'INTERSECT': pojedynczePTNZA['OUTPUT'],
                    'PREDICATE': [0],
                    'OUTPUT': 'memory:'
                })
                if extractbylocation['OUTPUT'].featureCount() > 0:
                    for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
            if isinstance(ptsoa, QgsVectorLayer):
                # bufor -0.02 m
                bufor = processing.run("native:buffer", {
                    'INPUT': ptsoa,
                    'DISTANCE': -0.02,
                    'SEGMENTS': 10,
                    'DISSOLVE': False,
                    'OUTPUT': 'memory:'
                })
                pojedynczePTSOA = processing.run("native:multiparttosingleparts", {
                    'INPUT': bufor['OUTPUT'],
                    'OUTPUT': 'memory:'
                })
                extractbylocation = processing.run("native:extractbylocation", {
                    'INPUT': layer, 
                    'INTERSECT': pojedynczePTSOA['OUTPUT'],
                    'PREDICATE': [0],
                    'OUTPUT': 'memory:'
                })
                if extractbylocation['OUTPUT'].featureCount() > 0:
                    for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
            if isinstance(ptwpa, QgsVectorLayer):
                # bufor -0.02 m
                bufor = processing.run("native:buffer", {
                    'INPUT': ptwpa,
                    'DISTANCE': -0.02,
                    'SEGMENTS': 10,
                    'DISSOLVE': False,
                    'OUTPUT': 'memory:'
                })
                pojedynczePTWPA = processing.run("native:multiparttosingleparts", {
                    'INPUT': bufor['OUTPUT'],
                    'OUTPUT': 'memory:'
                })
                extractbylocation = processing.run("native:extractbylocation", {
                    'INPUT': layer, 
                    'INTERSECT': pojedynczePTWPA['OUTPUT'],
                    'PREDICATE': [0],
                    'OUTPUT': 'memory:'
                })
                if extractbylocation['OUTPUT'].featureCount() > 0:
                    for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
            if isinstance(ptwza, QgsVectorLayer):
                # bufor -0.02 m
                bufor = processing.run("native:buffer", {
                    'INPUT': ptwza,
                    'DISTANCE': -0.02,
                    'SEGMENTS': 10,
                    'DISSOLVE': False,
                    'OUTPUT': 'memory:'
                })
                pojedynczePTWZA = processing.run("native:multiparttosingleparts", {
                    'INPUT': bufor['OUTPUT'],
                    'OUTPUT': 'memory:'
                })
                extractbylocation = processing.run("native:extractbylocation", {
                    'INPUT': layer, 
                    'INTERSECT': pojedynczePTWZA['OUTPUT'],
                    'PREDICATE': [0],
                    'OUTPUT': 'memory:'
                })
                if extractbylocation['OUTPUT'].featureCount() > 0:
                    for obj in extractbylocation['OUTPUT'].getFeatures():
                        obiektyZbledami.append(obj)
    except Exception as e:
        print (f"błąd w  funkcji blednePolozeniePktWys: ", e)
    return obiektyZbledami


def kontrolaKodKarto10k219_1(layer):
    try:
        obiektyZbledami = []
        dodane_bledy_ids = set()
        
        if not layer.isValid():
            return []
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        
        if not sktr_layers or not skjz_layers:
            return []
        
        sktr = sktr_layers[0]
        skjz = skjz_layers[0]
        index_buin = QgsSpatialIndex(layer.getFeatures())
        index_sktr = QgsSpatialIndex(sktr.getFeatures())
        index_skjz = QgsSpatialIndex(skjz.getFeatures())
        
        granica = adjaMinus2cmBufor(layer)
        
        # Filtrowanie warstwy BUIN_L według kodu
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_219_1\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_layer = extract_result['OUTPUT']
        
        # Filtrowanie obiektów według 'rodzaj' oraz kontrola długości
        for obj in filtered_layer.getFeatures():
            
            # Sprawdzanie, czy rodzaj jest poprawny
            if obj['rodzaj'] != 'tunel':
                print(f"-> Niewłaściwy rodzaj: {obj['rodzaj']}")
                obiektyZbledami.append(obj)
                continue
            
            # Kontrola długości obiektu BUIN_L
            geom = obj.geometry()
            obj_length = geom.length()
            
            if obj_length < 25:
                # Sprawdzanie, czy obiekt dotyka granicy
                if not any(obj.geometry().intersects(boundary.geometry()) for boundary in granica.getFeatures()):
                    if obj.id() not in dodane_bledy_ids:
                        obiektyZbledami.append(obj)
                        dodane_bledy_ids.add(obj.id())
                    continue  # Odrzucenie obiektu, jeśli nie styka się z granicą
                    
            buffer_distance = 5
            buffered_geometry = geom.buffer(buffer_distance, 5)
            nearby_sktr_ids = index_sktr.intersects(buffered_geometry.boundingBox())
            nearby_sktr = [sktr.getFeature(fid) for fid in nearby_sktr_ids]
           
            sktr_valid_positions = []
            sktr_invalid_positions = []
            for sktr_obj in nearby_sktr:
                try:
                    if sktr_obj['polozenie'] == 'na powierzchni gruntu':
                        sktr_valid_positions.append(sktr_obj)
                    else:
                        sktr_invalid_positions.append(sktr_obj)  # Obiekt SKTR ma niewłaściwe położenie
                except KeyError:
                    sktr_invalid_positions.append(sktr_obj)  # Obiekt SKTR nie ma atrybutu 'polozenie'
                    
            # Sprawdzenie obiektów SKJZ w buforze
            nearby_skjz_ids = index_skjz.intersects(buffered_geometry.boundingBox())
            nearby_skjz = [skjz.getFeature(fid) for fid in nearby_skjz_ids]
            
            # Logika decyzji
            if len(sktr_valid_positions) > 0:
                continue
            
            if len(nearby_sktr) > 0 and len(nearby_skjz) == 0:
                continue
            
            # Jeśli w buforze znajdują się tylko obiekty SKTR z błędnym lub brakującym atrybutem 'polozenie'
            if len(sktr_invalid_positions) == len(nearby_sktr):
                continue
            
            # Jeśli żaden obiekt SKTR nie spełnia warunku
            if obj.id() not in dodane_bledy_ids:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj.id())
        return obiektyZbledami
    except Exception as e:
        print (f"funkcja kontrolaKodKarto10k219_1 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k219_2(layer):
    try:
        obiektyZbledami = []
        dodane_bledy_ids = set()  # Analiza unikalnych Id obiektów błędnych
        
        if not layer.isValid():
            return []
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        
        if not sktr_layers or not skjz_layers:
            return []
        
        sktr = sktr_layers[0]
        skjz = skjz_layers[0]
        index_buin = QgsSpatialIndex(layer.getFeatures())
        index_sktr = QgsSpatialIndex(sktr.getFeatures())
        index_skjz = QgsSpatialIndex(skjz.getFeatures())
        
        # Filtrowanie warstwy BUIN_L według kodu
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_219_2\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_layer = extract_result['OUTPUT']
      
        for obj in filtered_layer.getFeatures():
            
            if obj['rodzaj'] != 'tunel':
                obiektyZbledami.append(obj)
                continue
            
            # Kontrola długości obiektu BUIN_L
            geom = obj.geometry()
            obj_length = geom.length()
            
            if obj_length >= 25:
                if obj.id() not in dodane_bledy_ids:
                    obiektyZbledami.append(obj)
                    dodane_bledy_ids.add(obj.id())
                continue  # Odrzucenie obiektu z dalszej analizy, jeśli długość >= 25
                
            buffer_distance = 5
            buffered_geometry = geom.buffer(buffer_distance, 5)
            
            # Sprawdzenie obiektów SKTR w buforze
            nearby_sktr_ids = index_sktr.intersects(buffered_geometry.boundingBox())
            nearby_sktr = [sktr.getFeature(fid) for fid in nearby_sktr_ids]
            
            # Weryfikacja atrybutu 'położenie' w obiektach SKTR
            sktr_valid_positions = []
            sktr_invalid_positions = []  # Lista obiektów SKTR, które mają błędny lub brakujący atrybut 'polozenie'
            for sktr_obj in nearby_sktr:
                try:
                    if sktr_obj['polozenie'] == 'na powierzchni gruntu':
                        sktr_valid_positions.append(sktr_obj)
                    else:
                        sktr_invalid_positions.append(sktr_obj)  # Obiekt SKTR ma niewłaściwe położenie
                except KeyError:
                    sktr_invalid_positions.append(sktr_obj)  # Obiekt SKTR nie ma atrybutu 'polozenie'
                    
            # Sprawdzenie obiektów SKJZ w buforze
            nearby_skjz_ids = index_skjz.intersects(buffered_geometry.boundingBox())
            nearby_skjz = [skjz.getFeature(fid) for fid in nearby_skjz_ids]
            
            # Logika decyzji
            if len(sktr_valid_positions) > 0:
                continue
                
            if len(nearby_sktr) > 0 and len(nearby_skjz) == 0:
                continue
                
            # Jeśli w buforze znajdują się tylko obiekty SKTR z błędnym lub brakującym atrybutem 'polozenie'
            if len(sktr_invalid_positions) == len(nearby_sktr):
                continue
                
            # Jeśli żaden obiekt SKTR nie spełnia warunku
            if obj.id() not in dodane_bledy_ids:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj.id())
                
        return obiektyZbledami
    
    except Exception as e:
        print (f"funkcja kontrolaKodKarto10k219_2 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k220_1(layer):
    try:
        obiektyZbledami = []
        dodane_bledy_ids = set()
        
        if not layer.isValid():
            return []
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        
        if not sktr_layers or not skjz_layers:
            return []
        
        sktr = sktr_layers[0]
        skjz = skjz_layers[0]
        
        index_buin = QgsSpatialIndex(layer.getFeatures())
        index_sktr = QgsSpatialIndex(sktr.getFeatures())
        index_skjz = QgsSpatialIndex(skjz.getFeatures())
        granica = adjaMinus2cmBufor(layer)
        
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_220_1\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_layer = extract_result['OUTPUT']
        filtered_features = list(filtered_layer.getFeatures())
        
        if len(filtered_features) == 0:
            return []
        
        # Filtrowanie obiektów według 'rodzaj' oraz kontrola długości i styku z granicą
        for obj in filtered_features:
            
            if obj['rodzaj'] not in ['estakada', 'wiadukt', 'most']:
                obiektyZbledami.append(obj)
                continue
                
            # Kontrola długości obiektu BUIN_L
            geom = obj.geometry()
            obj_length = geom.length()
            
            # Warunki długości i styku z granicą
            if obj_length < 10:
                # Sprawdzanie, czy obiekt dotyka granicy
                if not any(geom.intersects(boundary.geometry()) for boundary in granica.getFeatures()):
                    if obj.id() not in dodane_bledy_ids:
                        obiektyZbledami.append(obj)
                        dodane_bledy_ids.add(obj.id())
                    continue
                    
            buffer_distance = 5
            buffered_geometry = geom.buffer(buffer_distance, 5)
            
            # Sprawdzenie obiektów SKTR w buforze
            nearby_sktr_ids = index_sktr.intersects(buffered_geometry.boundingBox())
            nearby_sktr = [sktr.getFeature(fid) for fid in nearby_sktr_ids]
            
            # Weryfikacja atrybutu 'położenie' w obiektach SKTR
            sktr_valid_positions = []
            sktr_invalid_positions = []
            for sktr_obj in nearby_sktr:
                try:
                    if sktr_obj['polozenie'] == 'na powierzchni gruntu':
                        sktr_valid_positions.append(sktr_obj)
                    else:
                        sktr_invalid_positions.append(sktr_obj)
                except KeyError:
                    sktr_invalid_positions.append(sktr_obj)
                    
            # Sprawdzenie obiektów SKJZ w buforze
            nearby_skjz_ids = index_skjz.intersects(buffered_geometry.boundingBox())
            nearby_skjz = [skjz.getFeature(fid) for fid in nearby_skjz_ids]
            
            # Logika decyzji
            if len(sktr_valid_positions) > 0:
                continue
                
            if len(nearby_sktr) > 0 and len(nearby_skjz) == 0:
                continue
                
            if len(sktr_invalid_positions) == len(nearby_sktr):
                continue
                
            if obj.id() not in dodane_bledy_ids:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj.id())
                
        return obiektyZbledami
    except Exception as e:
        print (f"funkcja kontrolaKodKarto10k220_1 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k220_2(layer):
    try:
        obiektyZbledami = []
        dodane_bledy_ids = set()
        
        if not layer.isValid():
            return []
        
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        
        if not sktr_layers or not skjz_layers:
            return []
        
        sktr = sktr_layers[0]
        skjz = skjz_layers[0]
        
        index_buin = QgsSpatialIndex(layer.getFeatures())
        index_sktr = QgsSpatialIndex(sktr.getFeatures())
        index_skjz = QgsSpatialIndex(skjz.getFeatures())
        
        granica = adjaMinus2cmBufor(layer)
        
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_220_2\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_layer = extract_result['OUTPUT']
        filtered_features = list(filtered_layer.getFeatures())
        
        if len(filtered_features) == 0:
            return []
        
        for obj in filtered_features:
            if obj['rodzaj'] not in ['estakada', 'wiadukt', 'most']:
                obiektyZbledami.append(obj)
                continue
            
            geom = obj.geometry()
            obj_length = geom.length()
            
            buffer_distance = 5 
            buffered_geometry = geom.buffer(buffer_distance, 5)
            
            nearby_sktr_ids = index_sktr.intersects(buffered_geometry.boundingBox())
            nearby_sktr = [sktr.getFeature(fid) for fid in nearby_sktr_ids]
            
            nearby_skjz_ids = index_skjz.intersects(buffered_geometry.boundingBox())
            nearby_skjz = [skjz.getFeature(fid) for fid in nearby_skjz_ids]
            
            # Jeśli obiekt nie styka się z żadnym SKTR
            if len(nearby_sktr) == 0:
                if obj.id() not in dodane_bledy_ids:
                    obiektyZbledami.append(obj)
                    dodane_bledy_ids.add(obj.id())
                continue
            
            # Jeśli BUIN_L ma tylko SKTR w sąsiedztwie
            if len(nearby_sktr) > 0 and len(nearby_skjz) == 0:
                if 3 <= obj_length < 10:
                    continue
                
                if obj_length < 3:
                    if not any(obj.geometry().intersects(boundary.geometry()) for boundary in granica.getFeatures()):
                        if obj.id() not in dodane_bledy_ids:
                            obiektyZbledami.append(obj)
                            dodane_bledy_ids.add(obj.id())
                elif obj_length >= 10:
                    if obj.id() not in dodane_bledy_ids:
                        obiektyZbledami.append(obj)
                        dodane_bledy_ids.add(obj.id())
                continue
            
            sktr_valid_positions = []
            sktr_invalid_positions = []
            for sktr_obj in nearby_sktr:
                try:
                    if sktr_obj['polozenie'] == 'na powierzchni gruntu':
                        sktr_valid_positions.append(sktr_obj)
                    else:
                        sktr_invalid_positions.append(sktr_obj)
                except KeyError:
                    sktr_invalid_positions.append(sktr_obj)
                    
            if len(sktr_valid_positions) > 0:
                continue
            
            if len(sktr_invalid_positions) == len(nearby_sktr):
                continue
            
            if obj.id() not in dodane_bledy_ids:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj.id())
                
        return obiektyZbledami
    except Exception as e:
        print (f"funkcja kontrolaKodKarto10k220_2 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k131_1(layer):
    try:
        if not layer.isValid():
            return []
        
        obiektyZbledami = []
        dodane_bledy_ids = set()
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        
        if not skjz_layers or not sktr_layers:
            return []
        skjz = skjz_layers[0]
        sktr = sktr_layers[0]
        skjz_features = {f.id(): f for f in skjz.getFeatures()}
        sktr_features = {f.id(): f for f in sktr.getFeatures()}
        index_skjz = QgsSpatialIndex()
        for f in skjz_features.values():
            index_skjz.insertFeature(f)
        index_sktr = QgsSpatialIndex()
        for f in sktr_features.values():
            index_sktr.insertFeature(f)
        granica_features = list(adjaMinus2cmBufor(layer).getFeatures())
        if not granica_features:
            return []
        granica_geoms = [g.geometry() for g in granica_features]
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_131_1\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_features = list(extract_result['OUTPUT'].getFeatures())
        if not filtered_features:
            return []
        for obj in filtered_features:
            obj_id = obj.id()
            if obj_id in dodane_bledy_ids:
                continue
            if obj['rodzaj'] != 'tunel':
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj_id)
                continue
            geom = obj.geometry()
            if geom is None or geom.isEmpty():
                continue
            if geom.length() < 25:
                if not any(geom.intersects(gr_geom) for gr_geom in granica_geoms):
                    obiektyZbledami.append(obj)
                    dodane_bledy_ids.add(obj_id)
                    continue
            buffered_geom = geom.buffer(5, 5)
            if buffered_geom is None or buffered_geom.isEmpty():
                continue
            bbox = buffered_geom.boundingBox()
            nearby_skjz_ids = index_skjz.intersects(bbox)
            nearby_sktr_ids = index_sktr.intersects(bbox)
            nearby_skjz = [
                skjz_features[fid]
                for fid in nearby_skjz_ids
                if skjz_features[fid].geometry().intersects(buffered_geom)
            ]
            nearby_sktr = [
                sktr_features[fid]
                for fid in nearby_sktr_ids
                if sktr_features[fid].geometry().intersects(buffered_geom)
            ]
            skjz_valid = []
            skjz_invalid = []
            for f in nearby_skjz:
                try:
                    polozenie = f['polozenie']
                    if polozenie == 'na powierzchni gruntu':
                        skjz_valid.append(f)
                    else:
                        skjz_invalid.append(f)
                except KeyError:
                    skjz_invalid.append(f)
            if skjz_valid:
                continue
            if nearby_skjz and not nearby_sktr:
                continue
            if len(skjz_invalid) == len(nearby_skjz):
                continue
            obiektyZbledami.append(obj)
            dodane_bledy_ids.add(obj_id)
        return obiektyZbledami
    except Exception as e:
        print(f"funkcja kontrolaKodKarto10k131_1 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k131_2(layer):
    try:
        obiektyZbledami = []
        dodane_bledy_ids = set()
        if not layer.isValid():
            return []
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        if not sktr_layers or not skjz_layers:
            return []
        sktr = sktr_layers[0]
        skjz = skjz_layers[0]
        index_buin = QgsSpatialIndex(layer.getFeatures())
        index_sktr = QgsSpatialIndex(sktr.getFeatures())
        index_skjz = QgsSpatialIndex(skjz.getFeatures())
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_131_2\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_layer = extract_result['OUTPUT']
        for obj in filtered_layer.getFeatures():
            if obj['rodzaj'] != 'tunel':
                obiektyZbledami.append(obj)
                continue
            geom = obj.geometry()
            obj_length = geom.length()
            
            if obj_length >= 25:
                if obj.id() not in dodane_bledy_ids:
                    obiektyZbledami.append(obj)
                    dodane_bledy_ids.add(obj.id())
                continue
            buffer_distance = 5
            buffered_geometry = geom.buffer(buffer_distance, 5)
            nearby_skjz_ids = index_skjz.intersects(buffered_geometry.boundingBox())
            nearby_skjz = [skjz.getFeature(fid) for fid in nearby_skjz_ids]
            skjz_valid_positions = []
            skjz_invalid_positions = []
            for skjz_obj in nearby_skjz:
                try:
                    if skjz_obj['polozenie'] == 'na powierzchni gruntu':
                        skjz_valid_positions.append(skjz_obj)
                    else:
                        skjz_invalid_positions.append(skjz_obj)
                except KeyError:
                    skjz_invalid_positions.append(skjz_obj)
            nearby_sktr_ids = index_sktr.intersects(buffered_geometry.boundingBox())
            nearby_sktr = [sktr.getFeature(fid) for fid in nearby_sktr_ids]
            if len(skjz_valid_positions) > 0:
                continue
            if len(nearby_skjz) > 0 and len(nearby_sktr) == 0:
                continue
            if len(skjz_invalid_positions) == len(nearby_skjz):
                continue
            if obj.id() not in dodane_bledy_ids:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj.id())
        return obiektyZbledami
    except Exception as e:
        print (f"funkcja kontrolaKodKarto10k131_2 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k133_1(layer):
    try:
        if not layer.isValid():
            return []
        obiektyZbledami = []
        dodane_bledy_ids = set()
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        if not skjz_layers or not sktr_layers:
            return []
        skjz = skjz_layers[0]
        sktr = sktr_layers[0]
        skjz_features = {f.id(): f for f in skjz.getFeatures()}
        sktr_features = {f.id(): f for f in sktr.getFeatures()}
        index_skjz = QgsSpatialIndex()
        for f in skjz_features.values():
            index_skjz.insertFeature(f)
        index_sktr = QgsSpatialIndex()
        for f in sktr_features.values():
            index_sktr.insertFeature(f)
        granica_features = list(adjaMinus2cmBufor(layer).getFeatures())
        if not granica_features:
            return []
        granica_geoms = [g.geometry() for g in granica_features]
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_133_1\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_features = list(extract_result['OUTPUT'].getFeatures())
        if not filtered_features:
            return []
        for obj in filtered_features:
            obj_id = obj.id()
            if obj_id in dodane_bledy_ids:
                continue
            rodzaj = obj['rodzaj']
            if rodzaj not in ['estakada', 'wiadukt', 'most']:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj_id)
                continue
            geom = obj.geometry()
            if geom is None or geom.isEmpty():
                continue
            if geom.length() < 9.99:
                if not any(geom.intersects(g) for g in granica_geoms):
                    obiektyZbledami.append(obj)
                    dodane_bledy_ids.add(obj_id)
                    continue
            buffered_geom = geom.buffer(5, 5)
            if buffered_geom is None or buffered_geom.isEmpty():
                continue
            bbox = buffered_geom.boundingBox()
            nearby_skjz_ids = index_skjz.intersects(bbox)
            nearby_sktr_ids = index_sktr.intersects(bbox)
            nearby_skjz = [
                skjz_features[fid]
                for fid in nearby_skjz_ids
                if skjz_features[fid].geometry().intersects(buffered_geom)
            ]
            nearby_sktr = [
                sktr_features[fid]
                for fid in nearby_sktr_ids
                if sktr_features[fid].geometry().intersects(buffered_geom)
            ]
            skjz_valid = []
            skjz_invalid = []
            for f in nearby_skjz:
                try:
                    polozenie = f['polozenie']
                    if polozenie == 'na powierzchni gruntu':
                        skjz_valid.append(f)
                    else:
                        skjz_invalid.append(f)
                except KeyError:
                    skjz_invalid.append(f)
            if skjz_valid:
                continue
            if nearby_skjz and not nearby_sktr:
                continue
            if len(skjz_invalid) == len(nearby_skjz):
                continue
            obiektyZbledami.append(obj)
            dodane_bledy_ids.add(obj_id)
        return obiektyZbledami
    except Exception as e:
        print(f"funkcja kontrolaKodKarto10k133_1 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k133_2(layer):
    try:
        obiektyZbledami = []
        dodane_bledy_ids = set()
        
        if not layer.isValid():
            return []
        
        skjz_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKJZ_L")
        sktr_layer_name = layer.name().replace("OT_BUIN_L", "OT_SKTR_L")
        
        skjz_layers = QgsProject.instance().mapLayersByName(skjz_layer_name)
        sktr_layers = QgsProject.instance().mapLayersByName(sktr_layer_name)
        
        if not skjz_layers or not sktr_layers:
            return []
        
        skjz = skjz_layers[0]
        sktr = sktr_layers[0]
        
        index_buin = QgsSpatialIndex(layer.getFeatures())
        index_skjz = QgsSpatialIndex(skjz.getFeatures())
        index_sktr = QgsSpatialIndex(sktr.getFeatures())
        
        granica = adjaMinus2cmBufor(layer)
        distance_threshold = 10
        
        extract_params = {
            'INPUT': layer,
            'EXPRESSION': '"kodKarto10k" = \'0010_133_2\'',
            'OUTPUT': 'memory:'
        }
        extract_result = processing.run("native:extractbyexpression", extract_params)
        filtered_layer = extract_result['OUTPUT']
        
        for obj in filtered_layer.getFeatures():
            if obj.id() in dodane_bledy_ids:
                continue  # Jeśli obiekt już dodano, pomijamy dalsze sprawdzenia
            
            if obj['rodzaj'] not in ['estakada', 'wiadukt', 'most']:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj.id())
                continue
            
            obj_length = obj.geometry().length()
            obj_centroid = obj.geometry().centroid()
            
            nearby_skjz_ids = []
            nearby_sktr_ids = []
            
            for skjz_obj in skjz.getFeatures():
                if obj_centroid.distance(skjz_obj.geometry().centroid()) <= distance_threshold:
                    nearby_skjz_ids.append(skjz_obj.id())
                    
            for sktr_obj in sktr.getFeatures():
                if obj_centroid.distance(sktr_obj.geometry().centroid()) <= distance_threshold:
                    nearby_sktr_ids.append(sktr_obj.id())
                    
            if len(nearby_skjz_ids) > 0 and len(nearby_sktr_ids) == 0:
                if 3 <= obj_length < 10:
                    continue
                    
                if obj_length < 3:
                    if not any(obj.geometry().intersects(boundary.geometry()) for boundary in granica.getFeatures()):
                        obiektyZbledami.append(obj)
                        dodane_bledy_ids.add(obj.id())
                elif obj_length >= 10:
                    obiektyZbledami.append(obj)
                    dodane_bledy_ids.add(obj.id())
                
                if 3 <= obj_length < 10:
                    for skjz_obj in skjz.getFeatures():
                        if obj.geometry().distance(skjz_obj.geometry()) < distance_threshold:
                            if skjz_obj['polozenie'] is None or skjz_obj['polozenie'] == 'na powierzchni gruntu':
                                obiektyZbledami.append(obj)
                                dodane_bledy_ids.add(obj.id())
                                break
                        
            elif len(nearby_sktr_ids) > 0 and len(nearby_skjz_ids) == 0:
                obiektyZbledami.append(obj)
                dodane_bledy_ids.add(obj.id())
                
            else:
                for sktr_obj in sktr.getFeatures():
                    if obj.geometry().distance(sktr_obj.geometry()) < distance_threshold:
                        polozenie = sktr_obj['polozenie']
                        if polozenie is None or polozenie.strip().lower() != 'na powierzchni gruntu':
                            obiektyZbledami.append(obj)
                            dodane_bledy_ids.add(obj.id())
                            break
        
        return obiektyZbledami
    
    except Exception as e:
        print (f"funkcja kontrolaKodKarto10k133_2 nie zadziała poprawnie z powodu błędu {e}. Zostanie zwrócona pusta tablica")
        return []


def kontrolaKodKarto10k134_1(layer):
    try:
        obiektyZbledami = []
        granica = adjaMinus2cmBufor(layer)
        for obj in layer.getFeatures():
            try:
                if obj["kodKarto10k"] == '0010_134_1':
                    if obj['rodzaj'] != 'kładka':
                        obiektyZbledami.append(obj)
                        continue
                    geom = obj.geometry()
                    touches_boundary = any(geom.intersects(boundary.geometry()) for boundary in granica.getFeatures())
                    if not touches_boundary:
                        if not (geom.length() >= 10):
                            obiektyZbledami.append(obj)
            except Exception as e:
                 print ("błąd w obliczaniu kodu karto 0010_134_1:", e)
        return obiektyZbledami
    
    except Exception as x:
        print (f'funkcja kontrolaKodKarto10k134_1 nie zadziałała poprawnie z powodu błędu {x}. Zostanie zwrócona pusta tablica')
        return []


def kontrolaKodKarto10k134_2(layer):
    try:
        obiektyZbledami = []
        granica = adjaMinus2cmBufor(layer)
        for obj in layer.getFeatures():
            try:
                if obj["kodKarto10k"] == '0010_134_2':
                    if obj['rodzaj'] != 'kładka':
                        obiektyZbledami.append(obj)
                        continue
                    geom = obj.geometry()
                    touches_boundary = any(obj.geometry().intersects(boundary.geometry()) for boundary in granica.getFeatures())
                    if not touches_boundary:
                        if geom.length() < 3 or geom.length() > 10:
                            obiektyZbledami.append(obj)
            except Exception as e:
                 print ("błąd w obliczaniu kodu karto 0010_134_2:", e)
        return obiektyZbledami
        
    except Exception as x:
        print (f'funkcja kontrolaKodKarto10k134_2 nie zadziałała poprawnie z powodu błędu {x}. Zostanie zwrócona pusta tablica')
        return []


def kontrolaKodKarto10k140(layer):
    try:
        obiektyZbledami = []
        dozwolone_klasy = ['OT_PTKM_A', 'OT_PTPL_A', 'OT_PTZB_A']
        klasa = layer.name()[-9:]
        if klasa not in dozwolone_klasy:
            return []
        wszystkie_warstwy = QgsProject.instance().mapLayers().values()
        kuko_layer = None
        for lyr in wszystkie_warstwy:
            if lyr.name().endswith("OT_KUKO_A"):
                kuko_layer = lyr
                break
        if klasa == "OT_PTKM_A" and kuko_layer:
            index_kuko = QgsSpatialIndex()
            kuko_dict = {}
            for f in kuko_layer.getFeatures():
                geom = f.geometry()
                if geom and not geom.isEmpty():
                    index_kuko.insertFeature(f)
                    kuko_dict[f.id()] = f
        for f in layer.getFeatures():
            try:
                kod = str(f["kodKarto10k"]).strip()
                if klasa == "OT_PTKM_A" and kuko_layer:
                    geom = f.geometry()
                    if not geom or geom.isEmpty():
                        continue
                    candidate_ids = index_kuko.intersects(geom.boundingBox())
                    for cid in candidate_ids:
                        kuko_feat = kuko_dict.get(cid)
                        if kuko_feat is None:
                            continue
                        geom_kuko = kuko_feat.geometry()
                        rodzaj = str(kuko_feat["rodzaj"]).strip().lower()
                        if rodzaj in ["lotnisko lub lądowisko"]:
                            if geom.within(geom_kuko):
                                if kod != "0010_140":
                                    obiektyZbledami.append(f)
                                break
                elif klasa == "OT_PTPL_A":
                    naw = str(f["materialNawierzchni"]).strip().lower()
                    if naw not in ["grunt naturalny", "inny"]:
                        if kod != "0010_140":
                            obiektyZbledami.append(f)
                elif klasa == "OT_PTZB_A":
                    ros = str(f["roslinnosc"]).strip().lower()
                    if ros == "brak – plac twardy":
                        if kod != "0010_140":
                            obiektyZbledami.append(f)
            except Exception:
                pass
        return obiektyZbledami
    except Exception as e:
        print(f"Błąd w kontroli kodKarto10k140: {e}")
        return []


def kontrolaKodKarto10k638_1(layer):
    try:
        obiektyZbledami = []
        granica = adjaMinus2cmBufor(layer)
        for obj in layer.getFeatures():
            try:
                if obj["kodKarto10k"] == '0010_638_1':
                    if obj['rodzaj'] != 'akwedukt':
                        obiektyZbledami.append(obj)
                        continue
                    geom = obj.geometry()
                    touches_boundary = any(obj.geometry().intersects(boundary.geometry()) for boundary in granica.getFeatures())
                    if not touches_boundary:
                        if not (geom.length() >= 10):
                            obiektyZbledami.append(obj)
            except Exception as e:
                 print ("błąd w obliczaniu kodu karto 0010_638_1:", e)
        return obiektyZbledami
        
    except Exception as x:
        print (f'funkcja kontrolaKodKarto10k638_1 nie zadziałała poprawnie z powodu błędu {x}. Zostanie zwrócona pusta tablica')
        return []


def kontrolaKodKarto10k638_2(layer):
    try:
        obiektyZbledami = []
        granica = adjaMinus2cmBufor(layer)
        for obj in layer.getFeatures():
           try:
               if obj["kodKarto10k"] == '0010_638_2':
                   if obj['rodzaj'] != 'akwedukt':
                       obiektyZbledami.append(obj)
                       continue
                   geom = obj.geometry()
                   touches_boundary = any(obj.geometry().intersects(boundary.geometry()) for boundary in granica.getFeatures())
                   
                   if not touches_boundary:
                       if not (geom.length() < 10):
                           obiektyZbledami.append(obj)
           except Exception as e:
                print ("błąd w obliczaniu kodu karto 0010_638_2:", e)
        return obiektyZbledami
        
    except Exception as x:
        print (f'funkcja kontrolaKodKarto10k638_2 nie zadziałała poprawnie z powodu błędu {x}. Zostanie zwrócona pusta tablica')
        return []


global lokalneIdWRamachKlas
lokalneIdWRamachKlas = {}

def unikalnoscLokalnyIdWPliku(layer, plikGML):
    obiektyZbledami = []
    
    ns = {'gml':'http://www.opengis.net/gml/3.2', 'ot':'urn:gugik:specyfikacje:gmlas:bazaDanychObiektowTopograficznych10k:2.0'}
    lokalnyIds_elem = plikGML.getroot().findall(".//ot:lokalnyId", ns)
    lokalnyIds = []
    
    for lokalnyId in lokalnyIds_elem:
        lokalnyIds.append(lokalnyId.text)
    
    unikalne = set()
    zdublowane = set()
    zdublowaneWRamachKlas = {}
    
    for element in lokalnyIds:
        if element in unikalne:
            zdublowane.add(element)
        else:
            unikalne.add(element)
    
    for element in unikalne:
        if element in lokalneIdWRamachKlas:
            zdublowaneWRamachKlas[element] = layer.name()
        else:
            lokalneIdWRamachKlas[element] = layer.name()
    
    zdublowane_lista = list(zdublowane)
    zdublowaneWRamachKlas_lista = list(zdublowaneWRamachKlas.keys())
    
    for obj in layer.getFeatures():
        if obj['lokalnyId'] in zdublowane_lista:
            gml_id = obj['gml_id'] + '| w ramach tej samej klasy'
            obj.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
            layer.updateFeature(obj)
            obiektyZbledami.append(obj)
            layer.commitChanges()
    
    for obj in layer.getFeatures():
        if obj['lokalnyId'] in zdublowaneWRamachKlas_lista:
            gml_id = obj['gml_id'] + '| w ramach klasy: ' + str(lokalneIdWRamachKlas[obj['lokalnyId']])
            obj.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
            layer.updateFeature(obj)
            obiektyZbledami.append(obj)
            layer.commitChanges()
    
    return obiektyZbledami


def kontrolaRelacjiDzialkaEwidencyjnaDoPunktGraniczny(layer):
    obiektyZbledami = []    
    try:
        PunktGranicyDzialkiEwidencyjnej_layer = QgsProject().instance().mapLayersByName(layer.name().replace("DzialkaEwidencyjna","PunktGraniczny"))[0]
    except:
        PunktGranicyDzialkiEwidencyjnej_layer = None
    
    fields =  layer.fields()  
    for obj in layer.getFeatures():
         jestBlad = False
         
         if 'punktGranicyDzialki_href' in [field.name() for field in fields] and isinstance(obj['punktGranicyDzialki_href'], list):
            if len(obj['punktGranicyDzialki_href']) < 3:
                jestBlad = True
         else:
            jestBlad = True
        
         if jestBlad:
            gml_id = obj['gml_id'] + '| brak min. 3 referencji na punkty graniczne'
            obj.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
            layer.updateFeature(obj)
            obiektyZbledami.append(obj)
            layer.commitChanges()
    
    if 'punktGranicyDzialki_href' in [field.name() for field in fields]:
        extractbyexpression = processing.run("native:extractbyexpression", {
            'INPUT': layer,
            'EXPRESSION': "'punktGranicyDzialki_href' IS NOT NULL",
            'FAIL_OUTPUT': 'memory:',
            'OUTPUT': 'memory:'
        })
    else:
        return obiektyZbledami
    
    if extractbyexpression['OUTPUT'].featureCount() > 0 and layer.featureCount() > 0:
        punktyGranicyDzialki_hrefy = set()
        
        gmlid_PunktyGraniczne = set()
        if PunktGranicyDzialkiEwidencyjnej_layer != None:
            for obj_PG in PunktGranicyDzialkiEwidencyjnej_layer.getFeatures():
                gmlid_PunktyGraniczne.add(obj_PG['gml_id'])
        
        for obj in extractbyexpression['OUTPUT'].getFeatures():
            gml_id = ''
            if isinstance(obj['punktGranicyDzialki_href'], list):
                for gmlid in obj['punktGranicyDzialki_href']:
                    if gmlid == '':
                        gml_id += ' pusta referencja na punkt graniczny;'
                    else:
                        punktyGranicyDzialki_hrefy.add(gmlid)
                        if not gmlid in gmlid_PunktyGraniczne:
                            gml_id += 'referencja "' + gmlid + '" na punkt graniczny, którego nie ma w pliku gml;'
            else:
                if obj['punktGranicyDzialki_href'] != NULL:
                    punktyGranicyDzialki_hrefy.add(obj['punktGranicyDzialki_href'])
            
            if gml_id != '':
                gml_id = obj['gml_id'] + '| ' + gml_id
                obj.setAttribute(layer.fields().indexFromName("gml_id"), gml_id)
                layer.updateFeature(obj)
                obiektyZbledami.append(obj)
                layer.commitChanges()
    
    return obiektyZbledami


    def kontrolaRelacjiGESRzedna(layer):
        obiektyZbledami = []
        
        warstwyRelacji = ['GES_UrzadzenieZwiazaneZPrzewodami',
                          'GES_PrzewodWodociagowy',
                          'GES_Przewod',
                          'GES_UrzadzeniaTowarzyszczaceLiniowe',
                          'GES_InneUrzadzeniaTowarzyszace',
                          'GES_PrzewodKanalizacyjny',
                          'GES_PrzewodElektroenergetyczny',
                          'GES_PrzewodGazowy',
                          'GES_PrzewodCieplowniczy',
                          'GES_PrzewodTelekomunikacyjny',
                          'GES_PrzewodSpecjalny',
                          'GES_PrzewodNiezidentyfikowany',
                          'GES_UrzadzeniaSiecWodociagowa',
                          'GES_UrzadzeniaSiecKanalizacyjna',
                          'GES_UrzadzeniaSiecElektroenergetyczna',
                          'GES_UrzadzeniaSiecCieplownicza',
                          'GES_UrzadzeniaSiecTelekomunikacyjna',
                          'GES_UrzadzeniaTechniczneSieciSpecjalnej',
                          'GES_UrzadzenieNiezidentyfikowane',
                          'GES_UrzadzeniaSiecGazowa'
                          ]
        
        wartosci = []   # Inicjalizacja pustej listy na wartości
        
        for warstwa in warstwyRelacji:
            try:
                Rel_Obj_layer = QgsProject().instance().mapLayersByName(layer.name().replace("GES_Rzedna", warstwa))[0]
            except:
                continue
            
            fields = Rel_Obj_layer.fields()
            
            for obj in Rel_Obj_layer.getFeatures():
                if 'rzednaObiektu_href' in [field.name() for field in fields]:
                    if isinstance(obj['rzednaObiektu_href'], list):
                        for element in obj['rzednaObiektu_href']:
                            if isinstance(element, str):
                                wartosci.extend([val.replace("#", "") for val in element.split(',')])
                    else:
                        wartosci.append(str(obj['rzednaObiektu_href']).replace("#", ""))
                        
        for f in layer.getFeatures():
            gml_id = f['gml_id']
            if gml_id not in wartosci:
                obiektyZbledami.append(f)
        
        return obiektyZbledami


def zapisWspolrzednychKonturow(layer, plikGML):
    obiektyZbledami = []
    pattern = re.compile(r"[0-9]{6}\.[0-9]{3,}")
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'gr': 'urn:gugik:specyfikacje:gmlas:mapaGlebowoRolnicza:1.0'}
    konturyGlebowe = plikGML.findall('.//gr:GR_KonturGlebowy', namespaces=ns)
    for konturGlebowy in konturyGlebowe:
            lokalnyId = konturGlebowy.find('.//gr:lokalnyId', namespaces=ns).text
            expression = f"\"lokalnyId\" = '{lokalnyId}'"
            request = QgsFeatureRequest().setFilterExpression(expression)
            geometria = konturGlebowy.findall('.//gr:geometria', namespaces=ns)
            for g in geometria:
                geom = g.findall('.//gml:Polygon', namespaces=ns)
                for e in geom:
                    ex = e.findall('.//gml:exterior', namespaces=ns)
                    for f in ex:
                        liner = f.findall('.//gml:LinearRing', namespaces=ns)
                        for l in liner:
                            posList = l.find('.//gml:posList', namespaces=ns).text
                            if pattern.search(posList):
                                    for feature in layer.getFeatures(request):
                                        obiektyZbledami.append(feature)
    
    return obiektyZbledami


def zapisWspolrzednychOdkrywek(layer, plikGML):
    obiektyZbledami = []
    pattern = re.compile(r"[0-9]{6}\.[0-9]{3,}")
    plikGML = plikGML.getroot()
    ns = {'gml': 'http://www.opengis.net/gml/3.2', 'gr': 'urn:gugik:specyfikacje:gmlas:mapaGlebowoRolnicza:1.0'}
    odkrywkiGlebowe = plikGML.findall('.//gr:GR_OdkrywkaGlebowa', namespaces=ns)
    for odkrywkaGlebowa in odkrywkiGlebowe:
            lokalnyId = odkrywkaGlebowa.find('.//gr:lokalnyId', namespaces=ns).text
            expression = f"\"lokalnyId\" = '{lokalnyId}'"
            request = QgsFeatureRequest().setFilterExpression(expression)
            geometria = odkrywkaGlebowa.findall('.//gr:geometria', namespaces=ns)
            for g in geometria:
                geom = g.findall('.//gml:Point', namespaces=ns)
                for e in geom:
                    pos = e.find('.//gml:pos', namespaces=ns).text
                    if pattern.search(pos):
                        for feature in layer.getFeatures(request):
                            obiektyZbledami.append(feature)
    
    return obiektyZbledami


def kontrolaGeometriiGML_luki(layer, plikGML, klasa):
    obiektyZbledami = set()
    global gml_id_set
    gml_id_set = set()
    nieobslugiwanyTypGeometrii = QgsVectorLayer("Point?crs=epsg:2180&field=gml_id:string(254)", "Geometrie o nieobsługiwanym typie", "memory")
    
    ns = {'gml':'http://www.opengis.net/gml/3.2',
          'ot':'bazaDanychObiektowTopograficznych500:1.0',
          'ges':'geodezyjnaEwidencjaSieciUzbrojeniaTerenu:1.0',
          'egb':'ewidencjaGruntowIBudynkow:1.0'
          }
    
    if 'OT_' in klasa:
        klasy = plikGML.getroot().findall(".//ot:" + klasa, ns)
    elif 'EGB_' in klasa:
        klasy = plikGML.getroot().findall(".//egb:" + klasa, ns)
    else:
        klasy = plikGML.getroot().findall(".//ges:" + klasa, ns)
    
    for klasa in klasy:
        gml_id = klasa.attrib.get("{http://www.opengis.net/gml/3.2}id")
        
        for curve in klasa.findall(".//gml:Curve", ns):
            unusual_segments = {}
            for segment in curve.findall(".//*", ns):
                # ArcByCenterPoint, ArcString, Arc, ArcStringByBulge, ArcByBulge
                # CircleByCenterPoint, Circle
                if 'Arc' in segment.tag.split('}')[-1] or 'Circle' in segment.tag.split('}')[-1]:
                    gml_id_set.add(gml_id)
                    break
    
    nieobslugiwanyTypGeometrii.startEditing()
    for gmlid in gml_id_set:
        nowyRekord = QgsFeature(nieobslugiwanyTypGeometrii.fields())
        nowyRekord.setAttribute(0, gmlid)
        nowyRekord.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(0, 0)))
        nieobslugiwanyTypGeometrii.addFeature(nowyRekord)
        nieobslugiwanyTypGeometrii.commitChanges()
        obiektyZbledami.add(nowyRekord)
    
    del nieobslugiwanyTypGeometrii
    
    return obiektyZbledami