import argparse
from pathlib import Path


from error_collector import ErrorCollector, write_json_file
from utils import check_environment_var, check_file_in_work, dxf_is_binary, get_dxf_file


"""
Dieses Programm liest eine dxf Datei und holt sich vom Layer RENAME die Angaben
für die Bereiche in der Anlage, welche Motoren, Sensoren und Aktoren welchem
Unterverteiler zugeordnet werden müssen. (Attribut KENNZEICHNUNG)
Alle Symbol Templates werden entsprechend der Richtungsangaben in DIRECTION nummeriert
DIRECTION kann TOP_BOTTOM, BOTTOM_TOP, LEFT_RIGHT oder RIGHT_LEFT sein

Ein Renamer Symbol enthält z.B. die folgenden Angaben: 
für POT-RA, POT-MA und POT-UC:

NAME1=POT-RA@@
NAME2=POT-MA@@
NAME3=POT-UC@@
KENNZEICHNUNG=A01+UC…
LAYER_NAME1=ILS_POT-RA
LAYER_NAME2=ILS_POT-MA
LAYER_NAME3=ILS_POT-UC
DIRECTION = LEFT_RIGHT

für BG, MB:

NAME1=BG-1@@@
NAME2=MB-1@@@
NAME3=
KENNZEICHNUNG=
LAYER_NAME1=ILS_Eingang
LAYER_NAME2=ILS_Ausgang
LAYER_NAME3=
DIRECTION = LEFT_RIGHT

oder nur zur Nummerierung der MA Symbole
NAME=MA-1@@
KENNZEICHNUNG=A01+UH00
LAYER_NAME=ILS_MOTOR
DIRECTION = TOP_BOTTOM/LEFT_RIGHT

Diese Symbole auf dem RENAMER Layer sind immer in einem Block mit einer Polylinie oder einen REchteck. 
Alle Sensoren, Motoren, Aktoren etc. innerhalb der zugehörigen Polylinie nehmen an der Nummerierung teil, falls diese das @ Zeichen enthalten, also noch ein Template sind. 

"""


def read_config_layers(config_path: Path) -> list:
    """
    Liest die enumerate.cfg und gibt die Layer zurück, auf denen nach Renamer-Blöcken gesucht werden soll.
    Die Konfiguration hat eine einfache Liste unter [Layers]:
    [Layers]
    RENAMER
    OTHER_LAYER
    """
    layers = []

    with open(config_path, 'r', encoding='utf-8') as f:
        in_layers_section = False
        for line in f:
            line = line.strip()

            # Überspringe leere Zeilen und Kommentare
            if not line or line.startswith('#') or line.startswith(';'):
                continue

            # Prüfe ob wir in der [Layers] Sektion sind
            if line.startswith('['):
                in_layers_section = line.lower() == '[layers]'
                continue

            # Wenn in [Layers], füge Layer hinzu
            if in_layers_section:
                layers.append(line)

    return layers


def extract_block_attributes(doc, insert) -> dict:
    """
    Extrahiert alle Attribute aus einem INSERT-Block.
    Unterstützt zweistufige Blockstruktur:
    - Äußerer Block enthält LWPOLYLINE und INSERT zu Attribut-Block
    - Innerer Attribut-Block enthält die eigentlichen Attribute

    Args:
        doc: DXF-Dokument
        insert: INSERT-Entity des äußeren Blocks

    Returns:
        Dictionary mit allen gefundenen Attributen
    """
    attributes = {}
    if insert.dxftype() != 'INSERT':
        return attributes

    # Prüfe zuerst ob der Block direkt Attribute hat (alte Struktur)
    # Sammle alle direkten Attribute
    direct_attribs = list(insert.attribs)
    if direct_attribs:
        for attrib in direct_attribs:
            tag = attrib.dxf.tag
            value = attrib.dxf.text
            attributes[tag] = value
        return attributes

    # Neue zweistufige Struktur: Suche im Block nach einem INSERT mit Attributen
    try:
        block_layout = doc.blocks.get(insert.dxf.name)
        for entity in block_layout:
            if entity.dxftype() == 'INSERT':
                inner_attribs = list(entity.attribs)
                if inner_attribs:
                    # Gefunden: innerer Block mit Attributen
                    for attrib in inner_attribs:
                        tag = attrib.dxf.tag
                        value = attrib.dxf.text
                        attributes[tag] = value
                    break  # Nur das erste INSERT mit Attributen verwenden
    except Exception as e:
        print(f"    Warnung: Fehler beim Extrahieren der Attribute aus Block: {e}")

    return attributes


def is_rectangle_polyline(points):
    """
    Prüft, ob eine Liste von Punkten ein Rechteck darstellt.
    Ein Rechteck hat genau 4 Eckpunkte (oder 5 wenn geschlossen mit identischem Start/End).

    Args:
        points: Liste von Punkten (Tupel mit mindestens x, y Koordinaten)

    Returns:
        True wenn es ein Rechteck ist, False sonst
    """
    if len(points) < 4:
        return False

    # Prüfe ob erster und letzter Punkt identisch sind (geschlossene Polylinie)
    first = (points[0][0], points[0][1])
    last = (points[-1][0], points[-1][1])

    if abs(first[0] - last[0]) < 0.001 and abs(first[1] - last[1]) < 0.001:
        # Geschlossene Polylinie - sollte 5 Punkte haben (4 Ecken + 1 Schließung)
        if len(points) == 5:
            return True
        # Wenn mehr als 5 Punkte, ist es kein einfaches Rechteck
        return False
    else:
        # Offene Polylinie - sollte genau 4 Punkte haben
        return len(points) == 4


def get_boundary_geometry(doc, insert):
    """
    Sucht im Block nach einem Rechteck oder einer geschlossenen Polylinie.
    Unterstützt zweistufige Blockstruktur (äußerer Block mit LWPOLYLINE + INSERT zu Attribut-Block).
    Gibt die Eckpunkte zurück.

    Returns:
        Tupel (transformed_points, is_rectangle) oder (None, None) wenn nichts gefunden
    """
    block_layout = doc.blocks.get(insert.dxf.name)

    # Suche nach LWPOLYLINE, POLYLINE oder geschlossenen Linien
    for entity in block_layout:
        if entity.dxftype() == 'LWPOLYLINE':
            # Get points and transform them
            points = list(entity.get_points())

            # Prüfe ob geschlossen (entweder Flag gesetzt oder erster == letzter Punkt)
            is_closed = entity.closed or (entity.dxf.flags & 1)
            if not is_closed and len(points) > 1:
                # Prüfe ob erster und letzter Punkt gleich sind
                first = points[0][:2]
                last = points[-1][:2]
                if abs(first[0] - last[0]) < 0.001 and abs(first[1] - last[1]) < 0.001:
                    is_closed = True

            if is_closed or len(points) >= 4:
                # Transform points relative to insert position
                insert_point = insert.dxf.insert
                transformed_points = []
                for p in points:
                    # Einfache Translation (ohne Rotation/Skalierung für ersten Ansatz)
                    transformed_points.append((
                        insert_point[0] + p[0],
                        insert_point[1] + p[1]
                    ))

                # Prüfe ob es ein Rechteck ist
                is_rect = is_rectangle_polyline(points)
                return transformed_points, is_rect

        elif entity.dxftype() == 'POLYLINE':
            if entity.is_closed:
                points = [(v.dxf.location.x, v.dxf.location.y) for v in entity.vertices]
                insert_point = insert.dxf.insert
                transformed_points = []
                for p in points:
                    transformed_points.append((
                        insert_point[0] + p[0],
                        insert_point[1] + p[1]
                    ))

                # Prüfe ob es ein Rechteck ist
                is_rect = is_rectangle_polyline(points)
                return transformed_points, is_rect

    return None, None


def point_in_polygon(point, polygon):
    """
    Prüft, ob ein Punkt innerhalb eines Polygons liegt (Ray-casting Algorithmus).
    """
    x, y = point
    n = len(polygon)
    inside = False

    p1x, p1y = polygon[0]
    for i in range(1, n + 1):
        p2x, p2y = polygon[i % n]
        if y > min(p1y, p2y):
            if y <= max(p1y, p2y):
                if x <= max(p1x, p2x):
                    if p1y != p2y:
                        xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
                    if p1x == p2x or x <= xinters:
                        inside = not inside
        p1x, p1y = p2x, p2y

    return inside


def find_symbols_in_boundary(doc, msp, boundary, target_layers, attributes):
    """
    Findet alle Symbole (INSERT-Blöcke) innerhalb des angegebenen Bereichs auf den Ziel-Layern.
    """
    symbols = []

    # Bestimme welche Layer durchsucht werden sollen
    search_layers = []
    for i in range(1, 4):  # NAME1, NAME2, NAME3
        layer_key = f"LAYER_NAME{i}"
        if layer_key in attributes and attributes[layer_key]:
            search_layers.append(attributes[layer_key])

    # Falls nur NAME/LAYER_NAME vorhanden
    if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]:
        search_layers.append(attributes["LAYER_NAME"])

    # Durchsuche alle INSERT-Blöcke
    for entity in msp.query('INSERT'):
        if entity.dxf.layer not in search_layers:
            continue

        # Prüfe ob der Block Attribute hat (direkt oder in zweistufiger Struktur)
        # Verwende extract_block_attributes um beides zu unterstützen
        symbol_attribs = extract_block_attributes(doc, entity)

        if not symbol_attribs:
            continue

        # Hole Position des Symbols
        pos = entity.dxf.insert
        point = (pos[0], pos[1])

        # Prüfe ob innerhalb des Bereichs
        if point_in_polygon(point, boundary):
            # Prüfe ob es ein Template ist (enthält @)
            has_template = False
            for value in symbol_attribs.values():
                if '@' in str(value):
                    has_template = True
                    break

            if has_template:
                symbols.append({
                    'entity': entity,
                    'position': point,
                    'attributes': symbol_attribs,
                    'layer': entity.dxf.layer
                })

    return symbols


def sort_symbols_by_direction(symbols, direction):
    """
    Sortiert die Symbole nach der angegebenen Richtung.
        TOP_BOTTOM/LEFT_RIGHT: nach Y absteigend, dann X aufsteigend
        TOP_BOTTOM/RIGHT_LEFT: nach Y absteigend, dann X absteigend
        BOTTOM_TOP/RIGHT_LEFT: nach Y aufsteigend, dann X absteigend
        BOTTOM_TOP/LEFT_RIGHT: nach Y aufsteigend, dann X aufsteigend

    default ist von links nach rechts wenn keine Angabe gemacht wird. Oder von oben nach unten
        TOP_BOTTOM: TOP_BOTTOM/LEFT_RIGHT
        BOTTOM_TOP: BOTTOM_TOP/LEFT_RIGHT
        LEFT_RIGHT: TOP_BOTTOM/LEFT_RIGHT
        RIGHT_LEFT: TOP_BOTTOM/RIGHT_LEFT 
    """
    if direction == "TOP_BOTTOM/LEFT_RIGHT" or direction == "TOP_BOTTOM" or direction == "LEFT_RIGHT":
        return sorted(symbols, key=lambda s: (-s['position'][1], s['position'][0]))
    elif direction == "TOP_BOTTOM/RIGHT_LEFT" or direction == "RIGHT_LEFT":
        return sorted(symbols, key=lambda s: (-s['position'][1], s['position'][0]))
    elif direction == "BOTTOM_TOP/RIGHT_LEFT":
        return sorted(symbols, key=lambda s: (s['position'][1], s['position'][0]))
    elif direction == "BOTTOM_TOP/LEFT_RIGHT"or direction == "BOTTOM_TOP":
        return sorted(symbols, key=lambda s: (s['position'][1], s['position'][0]))
    else:
        # Fallback: LEFT_RIGHT
        return sorted(symbols, key=lambda s: (s['position'][0], s['position'][1]))


def enumerate_symbols(symbols, attributes):
    """
    Nummeriert die Symbole durch und ersetzt die @@ Platzhalter.
    """
    counter = 1
    renamed = []

    for symbol in symbols:
        layer = symbol['layer']

        # Finde das passende NAME-Template für diesen Layer
        name_template = None
        for i in range(1, 4):
            layer_key = f"LAYER_NAME{i}"
            name_key = f"NAME{i}"
            if layer_key in attributes and attributes[layer_key] == layer:
                if name_key in attributes:
                    name_template = attributes[name_key]
                    break

        # Falls nur NAME/LAYER_NAME vorhanden
        if not name_template and "LAYER_NAME" in attributes and attributes["LAYER_NAME"] == layer:
            if "NAME" in attributes:
                name_template = attributes["NAME"]

        if not name_template:
            continue

        # Ersetze @-Zeichen durch Zahlen
        # Zähle wie viele @ im Template sind
        at_count = name_template.count('@')
        number_str = str(counter).zfill(at_count)

        # Aktualisiere Attribute im Symbol
        for attrib in symbol['entity'].attribs:
            old_value = attrib.dxf.text
            if '@' in old_value:
                # Ersetze @ durch die Nummer
                new_value = old_value.replace('@' * at_count, number_str)
                attrib.dxf.text = new_value

                renamed.append({
                    'position': symbol['position'],
                    'layer': layer,
                    'old_value': old_value,
                    'new_value': new_value
                })

        counter += 1

    return renamed


def get_renamer_config_key(attributes):
    """
    Erstellt einen eindeutigen Schlüssel für die Renamer-Block-Konfiguration.
    Blöcke mit demselben Schlüssel werden gruppiert und gemeinsam nummeriert.

    Args:
        attributes: Dictionary mit Block-Attributen

    Returns:
        Tuple mit (NAME-Patterns, LAYER_NAMEs, DIRECTION, KENNZEICHNUNG)
    """
    # Sammle NAME-Patterns (normalisiert: NAME oder NAME1-3)
    name_patterns = []
    if "NAME" in attributes and attributes["NAME"]:
        name_patterns.append(("NAME", attributes["NAME"]))
    for i in range(1, 4):
        name_key = f"NAME{i}"
        if name_key in attributes and attributes[name_key]:
            name_patterns.append((name_key, attributes[name_key]))

    # Sammle LAYER_NAMEs (normalisiert: LAYER_NAME oder LAYER_NAME1-3)
    layer_names = []
    if "LAYER_NAME" in attributes and attributes["LAYER_NAME"]:
        layer_names.append(("LAYER_NAME", attributes["LAYER_NAME"]))
    for i in range(1, 4):
        layer_key = f"LAYER_NAME{i}"
        if layer_key in attributes and attributes[layer_key]:
            layer_names.append((layer_key, attributes[layer_key]))

    # DIRECTION und KENNZEICHNUNG
    direction = attributes.get("DIRECTION", "")
    kennzeichnung = attributes.get("KENNZEICHNUNG", "")

    # Erstelle einen eindeutigen Schlüssel
    # Sortiere die Listen für konsistente Keys
    name_patterns.sort()
    layer_names.sort()

    return (tuple(name_patterns), tuple(layer_names), direction, kennzeichnung)


def validate_direction_with_geometry(direction, is_rectangle):
    """
    Validiert die DIRECTION-Angabe gegen die tatsächliche Geometrie.

    Args:
        direction: DIRECTION-Attribut (z.B. "LEFT_RIGHT", "POLYLINE_PATH")
        is_rectangle: True wenn die Polylinie ein Rechteck ist, False sonst

    Returns:
        Tupel (is_valid, error_message):
        - is_valid: True wenn Kombination gültig ist
        - error_message: Fehlermeldung wenn ungültig, sonst None
    """
    # POLYLINE_PATH-Richtungen erwarten komplexe Polylinien (keine Rechtecke)
    polyline_path_directions = ["POLYLINE_PATH"]

    # Rechteck/Standard-Richtungen erwarten einfache Rechtecke
    rectangle_directions = ["TOP_BOTTOM", "BOTTOM_TOP", "LEFT_RIGHT", "RIGHT_LEFT", 
        "TOP_BOTTOM/LEFT_RIGHT", "TOP_BOTTOM/RIGHT_LEFT", "BOTTOM_TOP/RIGHT_LEFT", "BOTTOM_TOP/LEFT_RIGHT"]

    if direction in polyline_path_directions:
        if is_rectangle:
            return False, f"DIRECTION '{direction}' erfordert eine komplexe Polylinie, aber ein Rechteck wurde gefunden"
        return True, None

    elif direction in rectangle_directions:
        if not is_rectangle:
            return False, f"DIRECTION '{direction}' erfordert ein Rechteck, aber eine komplexe Polylinie wurde gefunden"
        return True, None

    else:
        # Unbekannte DIRECTION - gebe Warnung, aber breche nicht ab
        return True, f"Unbekannte DIRECTION '{direction}' - Verarbeitung wird fortgesetzt"


def process_renamer_blocks(doc, msp, renamer_layers, error_collector):
    """
    Verarbeitet alle Renamer-Blöcke auf den angegebenen Layern.
    Gruppiert Blöcke mit identischer Konfiguration und nummeriert alle Symbole
    aus allen Blöcken einer Gruppe gemeinsam.
    """
    all_renamed = []

    # Erste Pass: Sammle und gruppiere alle Renamer-Blöcke
    renamer_groups = {}  # {config_key: [(insert, boundary, attributes, is_rectangle), ...]}

    for layer in renamer_layers:
        print(f"Durchsuche Layer: {layer}")

        # Finde alle INSERT-Blöcke auf diesem Layer
        for insert in msp.query(f'INSERT[layer=="{layer}"]'):
            # Extrahiere Attribute (unterstützt zweistufige Blockstruktur)
            attributes = extract_block_attributes(doc, insert)

            if not attributes:
                print(f"  Block ohne Attribute gefunden an Position {insert.dxf.insert}")
                continue

            # Prüfe ob es ein Renamer-Block ist
            has_name = "NAME" in attributes or "NAME1" in attributes
            has_direction = "DIRECTION" in attributes

            if not (has_name and has_direction):
                continue

            block_name = attributes.get('NAME', attributes.get('NAME1', 'UNKNOWN'))
            print(f"  Renamer-Block gefunden: {block_name}")
            print(f"    Direction: {attributes.get('DIRECTION', 'UNKNOWN')}")
            print(f"    Kennzeichnung: {attributes.get('KENNZEICHNUNG', 'N/A')}")

            # Finde Boundary (Rechteck oder Polylinie)
            boundary, is_rectangle = get_boundary_geometry(doc, insert)
            if boundary is None:
                error_msg = f"Keine Polylinie/Rechteck im Renamer-Block '{block_name}' an Position {insert.dxf.insert} gefunden"
                print(f"    FEHLER: {error_msg}")
                error_collector.add_errors({"missing_boundary": error_msg})
                continue

            print(f"    Boundary gefunden mit {len(boundary)} Punkten (Rechteck: {is_rectangle})")

            # Validiere DIRECTION gegen Geometrie
            direction = attributes.get("DIRECTION", "LEFT_RIGHT")
            is_valid, validation_error = validate_direction_with_geometry(direction, is_rectangle)

            if not is_valid:
                error_msg = f"Renamer-Block '{block_name}' an Position {insert.dxf.insert}: {validation_error}"
                print(f"    FEHLER: {error_msg}")
                error_collector.add_errors({"direction_geometry_mismatch": error_msg})
                continue
            elif validation_error:
                # Warnung bei unbekannter DIRECTION, aber fortfahren
                print(f"    WARNUNG: {validation_error}")
                error_collector.add_warnings({"unknown_direction": validation_error})

            # Erstelle Konfigurationsschlüssel und gruppiere
            config_key = get_renamer_config_key(attributes)
            if config_key not in renamer_groups:
                renamer_groups[config_key] = []
            renamer_groups[config_key].append((insert, boundary, attributes, is_rectangle))

    # Zweite Pass: Verarbeite jede Gruppe
    for config_key, group_blocks in renamer_groups.items():
        if not group_blocks:
            continue

        # Verwende die Attribute des ersten Blocks als Referenz (alle in der Gruppe haben identische Attribute)
        reference_attributes = group_blocks[0][2]
        direction = reference_attributes.get("DIRECTION", "LEFT_RIGHT")
        block_name = reference_attributes.get('NAME', reference_attributes.get('NAME1', 'UNKNOWN'))

        print(f"\n  Verarbeite Gruppe: {block_name} ({len(group_blocks)} Renamer-Blöcke)")

        # Sammle Symbole aus ALLEN Boundaries dieser Gruppe
        all_symbols = []
        seen_entities = {}  # {entity_id: (symbol, [list of renamer block positions])}

        for insert, boundary, attributes, is_rectangle in group_blocks:
            # Finde Symbole innerhalb dieses Bereichs
            symbols = find_symbols_in_boundary(doc, msp, boundary, renamer_layers, attributes)

            # Füge Symbole hinzu, aber nur wenn das Entity noch nicht gesehen wurde
            for symbol in symbols:
                entity_id = id(symbol['entity'])  # Eindeutige ID des Entity-Objekts
                renamer_pos = insert.dxf.insert
                
                if entity_id not in seen_entities:
                    seen_entities[entity_id] = (symbol, [renamer_pos])
                    all_symbols.append(symbol)
                else:
                    # Symbol wurde bereits in einem anderen Bereich gefunden - Warnung
                    existing_symbol, existing_positions = seen_entities[entity_id]
                    existing_positions.append(renamer_pos)
                    symbol_pos = symbol['position']
                    warning_msg = (
                        f"Symbol an Position ({symbol_pos[0]:.1f}, {symbol_pos[1]:.1f}) "
                        f"liegt in mehreren Renamer-Bereichen der Gruppe '{block_name}'. "
                        f"Gefunden in Renamer-Blöcken an Positionen: "
                        f"{', '.join([f'({p[0]:.1f}, {p[1]:.1f})' for p in existing_positions])}"
                    )
                    error_collector.add_warnings({"symbol_in_multiple_boundaries": warning_msg})

        print(f"    Gesamt {len(all_symbols)} eindeutige Template-Symbole aus {len(group_blocks)} Blöcken")

        if not all_symbols:
            continue

        # Sortiere alle Symbole zusammen nach Richtung
        sorted_symbols = sort_symbols_by_direction(all_symbols, direction)

        # Nummeriere alle Symbole als eine Sequenz
        renamed = enumerate_symbols(sorted_symbols, reference_attributes)
        all_renamed.extend(renamed)

        print(f"    {len(renamed)} Symbole nummeriert")

    return all_renamed


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Nummeriert Symbole in DXF-Dateien basierend auf Renamer-Blöcken', prog='create_numbers')
    parser.add_argument('-f', '--filename', action='store', required=True, help='DXF-Datei die verarbeitet werden soll', metavar='myfile.dxf')
    parser.add_argument('-e', '--errorfile', action='store', required=False, help='JSON-Datei für Fehler und Warnungen', metavar='errors.json')
    parser.add_argument('-w', '--write', action='store', help='Schreibe Ergebnisse der Nummerierung in eine JSON-Datei')
    parser.add_argument('-d', '--dryrun', action='store_true', help='Symbole nicht in der DXF-Datei überschreiben, nur Ausgabe auf Konsole')

    args = parser.parse_args()

    out_dir = check_environment_var('PROJECT_DATA')
    work_dir = check_environment_var('PROJECT_WORK')
    config_dir = check_environment_var("PROJECT_CFG")

    # Initialisiere ErrorCollector
    error_collector = ErrorCollector()

    filename = Path(args.filename)
    if not filename.suffix == ".dxf":
        print("Nur für .dxf Dateien verfügbar")
        exit(1)

    (dxf_path, dexists) = check_file_in_work(work_dir, filename)
    if dexists == False:
        print(f"Datei nicht gefunden: {filename}")
        parser.print_help()
        exit(1)

    # Lese Konfiguration
    config_path = Path(config_dir) / "enumerate.cfg"
    if not config_path.exists():
        print(f"Konfigurationsdatei nicht gefunden: {config_path}")
        exit(1)

    renamer_layers = read_config_layers(config_path)
    print(f"Konfigurierte Layer: {renamer_layers}")

    if not renamer_layers:
        print("Keine Layer in der Konfiguration gefunden")
        exit(1)

    # Lese DXF-Datei
    if dxf_is_binary(dxf_path):
        print("DXF-Datei ist binär. Lese komplette Datei. Achten Sie auf RAM-Nutzung.")
        doc = get_dxf_file(dxf_path)
        msp = doc.modelspace()
    else:
        print("DXF-Datei ist ASCII.")
        doc = get_dxf_file(dxf_path)
        msp = doc.modelspace()

    # Verarbeite Renamer-Blöcke
    print("\n" + "="*60)
    print("Starte Verarbeitung der Renamer-Blöcke")
    print("="*60 + "\n")

    renamed_symbols = process_renamer_blocks(doc, msp, renamer_layers, error_collector)

    print("\n" + "="*60)
    print(f"Verarbeitung abgeschlossen: {len(renamed_symbols)} Symbole nummeriert")
    print("="*60 + "\n")

    # Ausgabe der Ergebnisse
    if renamed_symbols:
        print("Nummerierte Symbole:")
        for item in renamed_symbols:
            print(f"  {item['old_value']} -> {item['new_value']} (Layer: {item['layer']}, Pos: {item['position']})")

    # Speichere DXF-Datei wenn nicht dry-run
    if not args.dryrun:
        output_path = dxf_path.parent / f"{dxf_path.stem}_numbered{dxf_path.suffix}"
        doc.saveas(output_path)
        print(f"\nNummerierte DXF-Datei gespeichert: {output_path}")
    else:
        print("\nDry-run Modus: DXF-Datei wurde nicht überschrieben")

    # Schreibe JSON-Ausgabe wenn gewünscht
    if args.write:
        output_data = {
            'renamed_symbols': renamed_symbols,
            'total_count': len(renamed_symbols)
        }
        output_path = Path(args.write)
        write_json_file(output_data, output_path.parent, output_path.name)
        print(f"Ergebnisse in JSON gespeichert: {args.write}")

    # Schreibe Fehler-Datei nur wenn Fehler oder Warnungen vorhanden sind
    if error_collector.has_errors_or_warnings():
        if args.errorfile:
            error_data = error_collector.get_all_issues()
            error_path = Path(args.errorfile)
            write_json_file(error_data, error_path.parent, error_path.name)
            print(f"Fehler/Warnungen gespeichert: {args.errorfile}")
        else:
            # Keine explizite Fehlerdatei angegeben, generiere eine basierend auf Input-Datei
            error_filename = f"{dxf_path.stem}_errors.json"
            error_data = error_collector.get_all_issues()
            write_json_file(error_data, dxf_path.parent, error_filename)
            print(f"Fehler/Warnungen gespeichert: {dxf_path.parent / error_filename}")
    else:
        print("\nKeine Fehler oder Warnungen - keine Fehlerdatei wird geschrieben")

    # Exit-Code basierend auf Fehlern
    if error_collector.has_errors():
        print("\n!!! Es sind Fehler aufgetreten !!!")
        exit(1)
    elif error_collector.has_errors_or_warnings():
        print("\n(Warnungen vorhanden, aber keine kritischen Fehler)")
        exit(0)
    else:
        exit(0)
