import argparse
import json
import re
import sys
from pathlib import Path
from configparser import RawConfigParser
from fnmatch import fnmatch

import ezdxf
from openpyxl import Workbook

from utils import check_environment_var


"""
Dieses Programm:
- liest eine DXF Datei und sammelt alle TEXT und MTEXT Objekte
  ODER liest Texte aus einer TXT Datei (ein Text pro Zeile)
  ODER liest den 'untranslated' Block aus einer JSON Datei
  ODER übersetzt TEXT/MTEXT direkt in einer DXF und erstellt neue übersetzte DXF
- filtert die Texte basierend auf Mustern in translator.cfg
- übersetzt optional Texte mit Trigrammen, Bigrammen und Einzelwörtern aus translation/<LANG>.cfg
- schreibt die Ergebnisse in eine Excel-, JSON- oder Text-Datei
- speichert die Ausgabedatei im PROJECT_WORK Verzeichnis
"""


def load_translation_config(translation_lang: str, translation_dir: Path) -> tuple[dict[str, str], dict[str, str], dict[str, str], dict[str, str]]:
    """
    Lädt eine Übersetzungs-Config-Datei (z.B. CS.cfg, EN.cfg, FR.cfg).

    Args:
        translation_lang: Sprach-Code (z.B. "CS", "EN", "FR")
        translation_dir: Verzeichnis mit Übersetzungs-Configs (aus PROJECT_TRANSLATION)

    Returns:
        Tuple mit (Multi-Dict, Trigramme-Dict, Bigramme-Dict, Single-Dict)
    """
    config_file = translation_dir / f"{translation_lang}.cfg"

    if not config_file.exists():
        print(f"FEHLER: Übersetzungs-Konfiguration nicht gefunden: {config_file}")
        sys.exit(1)

    # RawConfigParser mit case-sensitive Keys
    config = RawConfigParser()
    # Überschreibe optionxform um Groß-/Kleinschreibung zu erhalten
    config.optionxform = str
    try:
        config.read(config_file, encoding='utf-8')
    except Exception as e:
        print(f"Fehler beim Lesen der Übersetzungs-Konfiguration: {e}")
        sys.exit(1)

    multi = {}
    trigramme = {}
    bigramme = {}
    single = {}

    # Lade Multi (4+ Wörter)
    if config.has_section('multi'):
        for key in config['multi']:
            original = key
            translation = config['multi'][key]
            if translation and not translation.startswith('#'):
                multi[original] = translation

    # Lade Trigramme
    if config.has_section('trigramme'):
        for key in config['trigramme']:
            original = key
            translation = config['trigramme'][key]
            if translation and not translation.startswith('#'):
                trigramme[original] = translation

    # Lade Bigramme
    if config.has_section('bigramme'):
        for key in config['bigramme']:
            original = key
            translation = config['bigramme'][key]
            if translation and not translation.startswith('#'):
                bigramme[original] = translation

    # Lade Single
    if config.has_section('single'):
        for key in config['single']:
            original = key
            translation = config['single'][key]
            if translation and not translation.startswith('#'):
                single[original] = translation

    print(f"\nÜbersetzungs-Config geladen ({translation_lang}):")
    print(f"  Multi-Phrasen: {len(multi)}")
    print(f"  Trigramme: {len(trigramme)}")
    print(f"  Bigramme: {len(bigramme)}")
    print(f"  Einzelwörter: {len(single)}")

    return multi, trigramme, bigramme, single


def load_symbol_attribute_mapping(cfg_path: Path) -> dict[str, str]:
    """
    Liest die Zuordnung von Sprachcodes zu Symbol-Attributnamen aus der translator.cfg Datei.

    Verwendet den Abschnitt [symbol_attribute], z.B.:
      DE = TEXT-D
      EN = TEXT-E
      CS = TEXT-CS

    Args:
        cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG)

    Returns:
        Dictionary mit Sprachcode -> Attributname Mapping (z.B. "DE" -> "TEXT-D", "EN" -> "TEXT-EN")
    """
    config_file = cfg_path / "translator.cfg"

    if not config_file.exists():
        print(f"FEHLER: Konfigurationsdatei nicht gefunden: {config_file}")
        print(f"Bitte erstellen Sie die Datei 'translator.cfg' im Verzeichnis: {cfg_path}")
        sys.exit(1)

    config = RawConfigParser()
    config.optionxform = str
    try:
        config.read(config_file, encoding='utf-8')
    except Exception as e:
        print(f"Fehler beim Lesen der Konfigurationsdatei: {e}")
        sys.exit(1)

    symbol_mapping: dict[str, str] = {}

    # Lade Symbol-Attribut-Zuordnung
    if config.has_section('symbol_attribute'):
        for key in config['symbol_attribute']:
            attr_name = config['symbol_attribute'][key].strip()
            if attr_name and not attr_name.startswith('#'):
                symbol_mapping[key] = attr_name

    print(f"\nGeladene Symbol-Attribut-Zuordnungen: {len(symbol_mapping)}")
    for lang, attr in symbol_mapping.items():
        print(f"  {lang} -> {attr}")

    return symbol_mapping


def load_symbol_layer_mapping(cfg_path: Path) -> dict[str, str]:
    """
    Liest die Zuordnung von Sprachcodes zu Symbol-Layern aus der translator.cfg Datei.

    Verwendet den Abschnitt [symbol_layer], z.B.:
      DE = 0-0_TEXT-D
      EN = 0-0_TEXT-E
      CS = 0-0_TEXT-CS

    Args:
        cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG)

    Returns:
        Dictionary mit Sprachcode -> Layername Mapping
    """
    config_file = cfg_path / "translator.cfg"

    if not config_file.exists():
        print(f"FEHLER: Konfigurationsdatei nicht gefunden: {config_file}")
        print(f"Bitte erstellen Sie die Datei 'translator.cfg' im Verzeichnis: {cfg_path}")
        sys.exit(1)

    config = RawConfigParser()
    config.optionxform = str
    try:
        config.read(config_file, encoding='utf-8')
    except Exception as e:
        print(f"Fehler beim Lesen der Konfigurationsdatei: {e}")
        sys.exit(1)

    layer_mapping: dict[str, str] = {}

    if config.has_section('symbol_layer'):
        for key in config['symbol_layer']:
            layer_name = config['symbol_layer'][key].strip()
            if layer_name and not layer_name.startswith('#'):
                layer_mapping[key] = layer_name

    print(f"\nGeladene Symbol-Layer-Zuordnungen: {len(layer_mapping)}")
    for lang, layer in layer_mapping.items():
        print(f"  {lang} -> {layer}")

    return layer_mapping


def load_ignore_patterns(cfg_path: Path) -> tuple[list[str], list[str]]:
    """
    Liest die ignore_pattern aus der translator.cfg Datei.
    Unterstützt sowohl Wildcard-Muster (*,?) als auch reguläre Ausdrücke.

    Args:
        cfg_path: Pfad zum Konfigurationsverzeichnis (PROJECT_CFG)

    Returns:
        Tuple mit (Wildcard-Mustern, Regex-Mustern)
    """
    config_file = cfg_path / "translator.cfg"

    if not config_file.exists():
        print(f"FEHLER: Konfigurationsdatei nicht gefunden: {config_file}")
        print(f"Bitte erstellen Sie die Datei 'translator.cfg' im Verzeichnis: {cfg_path}")
        sys.exit(1)

    # RawConfigParser verhindert dass Backslashes in Regex-Mustern escaped werden
    config = RawConfigParser()
    # Überschreibe optionxform um Groß-/Kleinschreibung zu erhalten
    config.optionxform = str
    try:
        config.read(config_file, encoding='utf-8')
    except Exception as e:
        print(f"Fehler beim Lesen der Konfigurationsdatei: {e}")
        sys.exit(1)

    wildcard_patterns = []
    regex_patterns = []

    # Lade Wildcard-Muster
    if config.has_section('ignore_pattern_wildcard'):
        for key in config['ignore_pattern_wildcard']:
            pattern = config['ignore_pattern_wildcard'][key].strip()
            if pattern and not pattern.startswith('#'):
                wildcard_patterns.append(pattern)

    # Lade Regex-Muster
    if config.has_section('ignore_pattern_regex'):
        for key in config['ignore_pattern_regex']:
            pattern = config['ignore_pattern_regex'][key].strip()
            if pattern and not pattern.startswith('#'):
                regex_patterns.append(pattern)

    print(f"Geladene Wildcard-Muster: {len(wildcard_patterns)}")
    for pattern in wildcard_patterns:
        print(f"  - {pattern}")

    print(f"Geladene Regex-Muster: {len(regex_patterns)}")
    for pattern in regex_patterns:
        print(f"  - {pattern}")

    return wildcard_patterns, regex_patterns


def translate_text(text: str, multi: dict[str, str], trigramme: dict[str, str], bigramme: dict[str, str], single: dict[str, str]) -> str:
    """
    Übersetzt einen Text durch Ersetzen von Multi-Phrasen, Trigrammen, Bigrammen und Einzelwörtern.
    Ersetzungen erfolgen in dieser Reihenfolge: Multi → Trigramme → Bigramme → Einzelwörter.

    Bei Einzelwörtern werden auch Satzzeichen und eingebettete Wörter berücksichtigt:
    - "Weight:" wird zu "Hmotnost:" (Doppelpunkt bleibt erhalten)
    - "(Sub-distributor)" wird zu "(Rozvaděč)" (Klammern bleiben erhalten)
    - "\\fArial Black|b0|i0;Firewall 1" wird zu "\\fArial Black|b0|i0;Požární 1"
      (alle Wörter in einem Token werden durchsucht, Trennzeichen bleiben erhalten)
    Bindestriche im Wort (wie "Sub-distributor") bleiben Teil des Kern-Worts.

    Args:
        text: Zu übersetzender Text
        multi: Dictionary mit Multi-Wort-Phrasen (4+ Wörter)
        trigramme: Dictionary mit Drei-Wort-Phrasen
        bigramme: Dictionary mit Zwei-Wort-Phrasen
        single: Dictionary mit Einzelwörtern

    Returns:
        Übersetzter Text (leer wenn keine Übersetzung gefunden wurde)
    """
    translated_text = text
    has_translation = False

    # 1. Multi-Phrasen ersetzen (längste Phrasen zuerst)
    for original, translation in multi.items():
        if original in translated_text:
            translated_text = translated_text.replace(original, translation)
            has_translation = True

    # 2. Trigramme ersetzen
    for original, translation in trigramme.items():
        if original in translated_text:
            translated_text = translated_text.replace(original, translation)
            has_translation = True

    # 3. Bigramme ersetzen
    for original, translation in bigramme.items():
        if original in translated_text:
            translated_text = translated_text.replace(original, translation)
            has_translation = True

    # 4. Einzelwörter ersetzen
    words = translated_text.split()
    translated_words = []
    for word in words:
        if word in single:
            # Direkter Match
            translated_words.append(single[word])
            has_translation = True
        else:
            # Suche ALLE Wörter im Token und ersetze sie (z.B. "Black|b0|i0;Firewall" enthält "Black" und "Firewall")
            # Pattern matcht Wörter (Buchstaben, Zahlen, Unterstriche, Bindestriche)
            word_found = False

            def replace_word(match):
                nonlocal word_found, has_translation
                matched_word = match.group(0)
                if matched_word in single:
                    word_found = True
                    has_translation = True
                    return single[matched_word]
                return matched_word

            # Ersetze alle gefundenen Wörter im Token
            translated_word = re.sub(r'[\w-]+', replace_word, word)
            translated_words.append(translated_word)
    translated_text = ' '.join(translated_words)

    # Gib nur übersetzten Text zurück wenn tatsächlich etwas übersetzt wurde
    return translated_text if has_translation else ""


def should_ignore_text(text: str, wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> bool:
    """
    Prüft ob ein Text ignoriert werden soll basierend auf den Mustern.

    Args:
        text: Zu prüfender Text
        wildcard_patterns: Liste der Wildcard-Muster (*,?)
        regex_patterns: Liste der regulären Ausdrücke
        debug: Wenn True, gibt Debug-Informationen aus

    Returns:
        True wenn Text ignoriert werden soll, False sonst
    """
    # Prüfe Wildcard-Muster
    for pattern in wildcard_patterns:
        if fnmatch(text, pattern):
            if debug:
                print(f"  DEBUG: '{text}' matched wildcard '{pattern}'")
            return True

    # Prüfe Regex-Muster
    for pattern in regex_patterns:
        try:
            if re.search(pattern, text):
                if debug:
                    print(f"  DEBUG: '{text}' matched regex '{pattern}'")
                return True
        except re.error:
            # Falls ungültiger Regex, ignoriere dieses Muster
            print(f"WARNUNG: Ungültiger regulärer Ausdruck: {pattern}")
            continue

    return False


def extract_text_from_dxf(filename: Path, text_attr_name: str = 'TEXT-D') -> list[str]:
    """
    Extrahiert alle TEXT und MTEXT Objekte sowie TEXT-{attr} Attribute aus Blöcken einer DXF Datei.
    Verwendet ein Set, um Duplikate zu vermeiden.
    Filtert leere Texte heraus.

    Args:
        filename: Pfad zur DXF Datei
        text_attr_name: Name des Text-Attributs in Blöcken (z.B. "TEXT-D", "TEXT-EN", "TEXT-CS"). Standard: "TEXT-D"

    Returns:
        Alphabetisch sortierte Liste aller gefundenen Texte (ohne Duplikate, ohne leere Texte)
    """
    texts_set = set()  # Set für eindeutige Texte
    text_count = 0
    mtext_count = 0
    block_count = 0
    empty_count = 0

    # Attribute die ein Block haben muss, damit das Text-Attribut extrahiert wird
    required_attributes = {'IO', 'ID', 'VERW', 'BEZEICHNUNG', 'KENNZEICHNUNG', text_attr_name}

    try:
        doc = ezdxf.readfile(filename)
        msp = doc.modelspace()

        # Sammle alle TEXT Objekte
        for entity in msp.query('TEXT'):
            if entity.dxf.text:
                text_count += 1
                text = entity.dxf.text.strip()  # Entferne führende/nachfolgende Leerzeichen

                # Ignoriere leere Texte
                if not text:
                    empty_count += 1
                    continue

                texts_set.add(text)

        # Sammle alle MTEXT Objekte
        for entity in msp.query('MTEXT'):
            if entity.text:
                mtext_count += 1
                text = entity.text.strip()  # Entferne führende/nachfolgende Leerzeichen

                # Ignoriere leere Texte
                if not text:
                    empty_count += 1
                    continue

                texts_set.add(text)

        # Sammle TEXT-D aus Blöcken mit den erforderlichen Attributen
        for entity in msp.query('INSERT'):
            if entity.has_attrib:
                # Sammle alle vorhandenen Attribute des Blocks
                block_attributes = set()
                text_d_value = None

                for attrib in entity.attribs:
                    block_attributes.add(attrib.dxf.tag)
                    if attrib.dxf.tag == text_attr_name:
                        text_d_value = attrib.dxf.text.strip() if attrib.dxf.text else None

                # Prüfe ob Block alle erforderlichen Attribute hat
                if required_attributes.issubset(block_attributes) and text_d_value:
                    block_count += 1

                    # Ignoriere leere Texte
                    if not text_d_value:
                        empty_count += 1
                        continue

                    texts_set.add(text_d_value)

        # Konvertiere Set zu sortierter Liste
        texts = sorted(texts_set)

        print(f"Anzahl gefundener TEXT Objekte: {text_count}")
        print(f"Anzahl gefundener MTEXT Objekte: {mtext_count}")
        print(f"Anzahl gefundener {text_attr_name} in Blöcken: {block_count}")
        print(f"Leere Texte ignoriert: {empty_count}")
        total_count = text_count + mtext_count + block_count
        print(f"Duplikate entfernt: {total_count - empty_count - len(texts)}")
        print(f"Eindeutige Texte extrahiert: {len(texts)}")

    except Exception as e:
        print(f"Fehler beim Lesen der DXF Datei: {e}")
        sys.exit(1)

    return texts


def filter_texts(texts: list[str], wildcard_patterns: list[str], regex_patterns: list[str], debug: bool = False) -> tuple[list[str], list[str]]:
    """
    Filtert eine Liste von Texten basierend auf Wildcard- und Regex-Mustern.
    Diese Funktion ist unabhängig von der Quelle der Texte (DXF oder TXT).

    Args:
        texts: Liste der zu filternden Texte
        wildcard_patterns: Liste der Wildcard-Muster zum Filtern
        regex_patterns: Liste der Regex-Muster zum Filtern
        debug: Wenn True, gibt Debug-Informationen aus

    Returns:
        Tuple mit (akzeptierte Texte, ignorierte Texte) - beide alphabetisch sortiert
    """
    accepted_texts = []
    ignored_texts = []

    for text in texts:
        if should_ignore_text(text, wildcard_patterns, regex_patterns, debug):
            ignored_texts.append(text)
        else:
            accepted_texts.append(text)

    # Sortiere beide Listen alphabetisch
    accepted_texts.sort()
    ignored_texts.sort()

    print(f"\nFilterung abgeschlossen:")
    print(f"  Akzeptierte Texte: {len(accepted_texts)}")
    print(f"  Ignorierte Texte: {len(ignored_texts)}")

    return accepted_texts, ignored_texts


def read_texts_from_file(filename: Path) -> list[str]:
    """
    Liest Texte aus einer UTF-8 Textdatei (ein Text pro Zeile).
    Entfernt Duplikate und leere Zeilen.
    Ignoriert Abschnitts-Marker (Zeilen die mit "---" beginnen).

    Args:
        filename: Pfad zur Textdatei

    Returns:
        Alphabetisch sortierte Liste aller Texte (ohne Duplikate, ohne leere Zeilen)
    """
    texts_set = set()

    try:
        with open(filename, 'r', encoding='utf-8') as f:
            for line in f:
                text = line.strip()
                if text:  # Ignoriere leere Zeilen
                    # Ignoriere Abschnitts-Marker (z.B. "--- translations:", "--- ignored:")
                    if text.startswith('---'):
                        continue
                    # Konvertiere escaped Zeilenumbrüche zurück
                    text = text.replace('\\n', '\n').replace('\\r', '\r')
                    texts_set.add(text)

        texts = sorted(texts_set)
        print(f"Texte aus Datei gelesen: {len(texts)}")
        return texts

    except Exception as e:
        print(f"Fehler beim Lesen der Textdatei: {e}")
        sys.exit(1)


def detect_json_structure(data: dict) -> tuple[str, list[str]]:
    """
    Erkennt die Struktur einer JSON-Datei (alte oder hierarchische mehrsprachige Struktur).

    Args:
        data: Geladene JSON-Daten als Dictionary

    Returns:
        Tuple mit (Struktur-Typ, Liste der Sprachen)
        - Struktur-Typ: "legacy" (alte Struktur) oder "multilang" (mehrsprachige hierarchische Struktur)
        - Liste der Sprachen: leer bei legacy, z.B. ["EN", "FR", "CS"] bei multilang
    """
    # Prüfe ob alte Struktur (hat 'untranslated' oder 'translations' Keys)
    if 'untranslated' in data or 'translations' in data:
        return "legacy", []

    # Prüfe ob neue hierarchische Struktur (hat [LANG] Keys)
    languages = []
    for key in data.keys():
        if key.startswith('[') and key.endswith(']') and len(key) > 2:
            # Extrahiere Sprachcode aus [LANG]
            lang = key[1:-1]
            languages.append(lang)

    if languages:
        return "multilang", languages

    # Unbekannte Struktur
    return "unknown", []


def read_texts_from_json(filename: Path) -> list[str]:
    """
    Liest Texte aus einer JSON-Datei.
    Unterstützt beide Strukturen:
    - Alte Struktur: "untranslated": [{"source": "...", "target": ""}, ...]
    - Hierarchische Struktur: "[LANG]": {"untranslated": [...], ...}

    Args:
        filename: Pfad zur JSON-Datei

    Returns:
        Alphabetisch sortierte Liste aller "source" Texte aus den untranslated Blöcken
    """
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            data = json.load(f)

        if not isinstance(data, dict):
            print(f"FEHLER: JSON-Datei hat ungültige Struktur (erwartet Object, bekam {type(data).__name__})")
            sys.exit(1)

        # Erkenne Struktur
        structure_type, languages = detect_json_structure(data)

        texts_set = set()  # Verwende Set um Duplikate zu vermeiden

        if structure_type == "legacy":
            # Alte Struktur
            if 'untranslated' not in data:
                print(f"FEHLER: JSON-Datei enthält keinen 'untranslated' Block")
                sys.exit(1)

            untranslated = data['untranslated']
            if not isinstance(untranslated, list):
                print(f"FEHLER: 'untranslated' Block hat ungültige Struktur (erwartet Array)")
                sys.exit(1)

            for idx, entry in enumerate(untranslated):
                if not isinstance(entry, dict):
                    print(f"WARNUNG: Eintrag {idx} in 'untranslated' ist kein Object, überspringe")
                    continue

                if 'source' not in entry:
                    print(f"WARNUNG: Eintrag {idx} in 'untranslated' hat kein 'source' Feld, überspringe")
                    continue

                source_text = entry['source']
                if source_text:  # Ignoriere leere Texte
                    texts_set.add(source_text)

            print(f"Texte aus JSON-Datei gelesen (alte Struktur): {len(texts_set)}")

        elif structure_type == "multilang":
            # Hierarchische mehrsprachige Struktur
            print(f"Erkannte mehrsprachige Struktur mit Sprachen: {', '.join(languages)}")

            for lang in languages:
                lang_key = f"[{lang}]"
                if lang_key not in data:
                    continue

                lang_data = data[lang_key]
                if not isinstance(lang_data, dict):
                    print(f"WARNUNG: [{lang}] Block ist kein Object, überspringe")
                    continue

                # Lese aus untranslated Block
                if 'untranslated' in lang_data:
                    untranslated = lang_data['untranslated']
                    if not isinstance(untranslated, list):
                        print(f"WARNUNG: [{lang}][untranslated] ist kein Array, überspringe")
                        continue

                    for idx, entry in enumerate(untranslated):
                        if not isinstance(entry, dict):
                            continue

                        if 'source' not in entry:
                            continue

                        source_text = entry['source']
                        if source_text:
                            texts_set.add(source_text)

            print(f"Texte aus JSON-Datei gelesen (mehrsprachige Struktur): {len(texts_set)}")

        else:
            print(f"FEHLER: JSON-Datei hat unbekannte Struktur")
            sys.exit(1)

        # Konvertiere Set zu sortierter Liste
        texts = sorted(texts_set)
        return texts

    except json.JSONDecodeError as e:
        print(f"Fehler beim Parsen der JSON-Datei: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Fehler beim Lesen der JSON-Datei: {e}")
        sys.exit(1)


def load_translations_from_json(filename: Path, target_lang: str = None) -> dict[str, str]:
    """
    Liest Übersetzungen aus einer JSON-Datei.
    Unterstützt beide Strukturen:
    - Alte Struktur: "translations": [{"source": "...", "target": "..."}, ...]
    - Hierarchische Struktur: "[LANG]": {"translations": [...], ...}

    Bei hierarchischer Struktur werden Übersetzungen aus allen Sprachen oder nur der angegebenen Sprache gelesen.
    Wenn mehrere Übersetzungen für denselben source existieren, wird die letzte verwendet (Sprache mit höherer Priorität).

    Args:
        filename: Pfad zur JSON-Datei
        target_lang: Optional: Bei hierarchischer Struktur nur Übersetzungen dieser Sprache laden

    Returns:
        Dictionary mit source -> target Mappings
    """
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            data = json.load(f)

        if not isinstance(data, dict):
            print(f"FEHLER: JSON-Datei hat ungültige Struktur (erwartet Object, bekam {type(data).__name__})")
            sys.exit(1)

        # Erkenne Struktur
        structure_type, languages = detect_json_structure(data)

        translations = {}

        if structure_type == "legacy":
            # Alte Struktur
            if 'translations' not in data:
                print(f"FEHLER: JSON-Datei enthält keinen 'translations' Block")
                sys.exit(1)

            translations_list = data['translations']
            if not isinstance(translations_list, list):
                print(f"FEHLER: 'translations' Block hat ungültige Struktur (erwartet Array)")
                sys.exit(1)

            for idx, entry in enumerate(translations_list):
                if not isinstance(entry, dict):
                    print(f"WARNUNG: Eintrag {idx} in 'translations' ist kein Object, überspringe")
                    continue

                if 'source' not in entry or 'target' not in entry:
                    print(f"WARNUNG: Eintrag {idx} in 'translations' fehlt 'source' oder 'target', überspringe")
                    continue

                source = entry['source']
                target = entry['target']
                if source and target:  # Ignoriere leere Einträge
                    translations[source] = target

            print(f"Übersetzungen aus JSON-Datei geladen (alte Struktur): {len(translations)}")

        elif structure_type == "multilang":
            # Hierarchische mehrsprachige Struktur
            print(f"Erkannte mehrsprachige Struktur mit Sprachen: {', '.join(languages)}")

            # Filtere Sprachen falls target_lang angegeben
            langs_to_load = [target_lang] if target_lang and target_lang in languages else languages

            for lang in langs_to_load:
                lang_key = f"[{lang}]"
                if lang_key not in data:
                    continue

                lang_data = data[lang_key]
                if not isinstance(lang_data, dict):
                    print(f"WARNUNG: [{lang}] Block ist kein Object, überspringe")
                    continue

                # Lese aus translations Block (oder translated für Rückwärtskompatibilität)
                translations_key = 'translations' if 'translations' in lang_data else 'translated'
                if translations_key in lang_data:
                    translations_list = lang_data[translations_key]
                    if not isinstance(translations_list, list):
                        print(f"WARNUNG: [{lang}][{translations_key}] ist kein Array, überspringe")
                        continue

                    for idx, entry in enumerate(translations_list):
                        if not isinstance(entry, dict):
                            continue

                        if 'source' not in entry or 'target' not in entry:
                            continue

                        source = entry['source']
                        target = entry['target']
                        if source and target:
                            translations[source] = target

            if target_lang:
                print(f"Übersetzungen für Sprache [{target_lang}] aus JSON-Datei geladen: {len(translations)}")
            else:
                print(f"Übersetzungen aus JSON-Datei geladen (mehrsprachige Struktur, alle Sprachen): {len(translations)}")

        else:
            print(f"FEHLER: JSON-Datei hat unbekannte Struktur")
            sys.exit(1)

        return translations

    except json.JSONDecodeError as e:
        print(f"Fehler beim Parsen der JSON-Datei: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Fehler beim Lesen der JSON-Datei: {e}")
        sys.exit(1)


def ensure_block_attdef_and_layer(doc, block_name: str,
                                  text_source_attr: str,
                                  text_target_attr: str,
                                  target_layer: str | None = None) -> None:
    """
    Stellt sicher, dass im Block-Definitionseintrag ein ATTDEF für das Ziel-Attribut
    existiert und der passende Layer vorhanden ist.

    - Wenn der Layer aus translator.cfg ([symbol_layer]) noch nicht existiert, wird er angelegt.
    - Wenn das ATTDEF für text_target_attr noch nicht existiert, wird es auf Basis des
      ATTDEF von text_source_attr erzeugt (Position, Höhe, Stil, Rotation werden übernommen).
    """
    if doc is None or not block_name:
        return

    try:
        block = doc.blocks[block_name]
    except KeyError:
        # Kein Block mit diesem Namen vorhanden (z.B. anonyme Blöcke) -> nichts zu tun
        return

    # Layer sicherstellen
    if target_layer:
        if target_layer not in doc.layers:
            doc.layers.new(name=target_layer)

    source_def = None
    target_def = None

    for attdef in block.attdefs():
        tag = attdef.dxf.tag
        if tag == text_target_attr:
            target_def = attdef
        elif tag == text_source_attr:
            source_def = attdef

    # Ziel-ATTDEF existiert bereits -> ggf. nur Layer korrigieren
    if target_def is not None:
        if target_layer:
            target_def.dxf.layer = target_layer
        return

    # Neues ATTDEF anlegen, möglichst anhand der Quell-Definition
    insert = (0.0, 0.0)
    height = 2.5
    layer_name = target_layer or "0"

    if source_def is not None:
        insert = source_def.dxf.insert
        if source_def.dxf.hasattr('height'):
            height = source_def.dxf.height
        if source_def.dxf.hasattr('layer') and not target_layer:
            layer_name = source_def.dxf.layer

    # `add_attdef` unterstützt keinen `layer`-Parameter direkt, daher Layer danach setzen
    new_def = block.add_attdef(
        tag=text_target_attr,
        insert=insert,
        height=height,
    )
    new_def.dxf.layer = layer_name

    # Weitere Eigenschaften (Rotation, Stil) vom Quell-ATTDEF übernehmen
    if source_def is not None:
        if source_def.dxf.hasattr('rotation'):
            new_def.dxf.rotation = source_def.dxf.rotation
        if source_def.dxf.hasattr('style'):
            new_def.dxf.style = source_def.dxf.style


def translate_block_attribute(entity,
                              doc,
                              translations: dict[str, str],
                              wildcard_patterns: list[str],
                              regex_patterns: list[str],
                              required_attributes: set[str],
                              text_source_attr: str,
                              text_target_attr: str,
                              target_layer: str | None = None) -> tuple[int, int, int]:
    """
    Übersetzt Text-Attribute eines einzelnen Block-Inserts (INSERT entity).

    Args:
        entity: INSERT entity aus der DXF-Datei
        doc: ezdxf-Dokument, um Blockdefinitionen und Layer zu ändern
        translations: Dictionary mit source -> target Mappings
        wildcard_patterns: Wildcard-Muster zum Filtern
        regex_patterns: Regex-Muster zum Filtern
        required_attributes: Set mit erforderlichen Attributnamen für den Block
        text_source_attr: Quell-Attributname (z.B. "TEXT-D", "TEXT-EN")
        text_target_attr: Ziel-Attributname (z.B. "TEXT-EN", "TEXT-CS")
        target_layer: Ziel-Layername für das Ziel-Attribut (aus [symbol_layer])

    Returns:
        Tuple mit (translated_count, skipped_count, ignored_count) für diesen Block
    """
    if not entity.has_attrib:
        return 0, 0, 0

    # Sammle alle vorhandenen Attribute des Blocks
    block_attributes = {}
    text_source_attrib = None
    text_target_attrib = None

    for attrib in entity.attribs:
        block_attributes[attrib.dxf.tag] = attrib
        if attrib.dxf.tag == text_source_attr:
            text_source_attrib = attrib
        elif attrib.dxf.tag == text_target_attr:
            text_target_attrib = attrib

    # Prüfe ob Block alle erforderlichen Attribute hat
    if not (required_attributes.issubset(block_attributes.keys()) and text_source_attrib):
        return 0, 0, 0

    original_text = text_source_attrib.dxf.text.strip() if text_source_attrib.dxf.text else None

    if not original_text:
        return 0, 0, 0

    # Prüfe ob Text ignoriert werden soll
    if should_ignore_text(original_text, wildcard_patterns, regex_patterns):
        return 0, 0, 1

    # Suche Übersetzung
    if original_text not in translations:
        return 0, 1, 0

    # Schreibe Übersetzung in Ziel-Attribut
    if text_target_attrib:
        # Ziel-Attribut existiert bereits, aktualisiere es
        text_target_attrib.dxf.text = translations[original_text]
        # Layer ggf. anpassen
        if target_layer:
            text_target_attrib.dxf.layer = target_layer
    else:
        # Vor dem Anlegen sicherstellen, dass im Block ein passender ATTDEF existiert
        ensure_block_attdef_and_layer(doc, entity.dxf.name, text_source_attr, text_target_attr, target_layer)

        # Ziel-Attribut existiert nicht, erstelle es basierend auf Quell-Attribut
        new_attrib = entity.add_attrib(
            tag=text_target_attr,
            text=translations[original_text],
            insert=text_source_attrib.dxf.insert,
        )
        # Kopiere weitere Eigenschaften
        if text_source_attrib.dxf.hasattr('height'):
            new_attrib.dxf.height = text_source_attrib.dxf.height
        if text_source_attrib.dxf.hasattr('style'):
            new_attrib.dxf.style = text_source_attrib.dxf.style
        if text_source_attrib.dxf.hasattr('rotation'):
            new_attrib.dxf.rotation = text_source_attrib.dxf.rotation
        if text_source_attrib.dxf.hasattr('width'):
            new_attrib.dxf.width = text_source_attrib.dxf.width
        # Layer explizit auf Ziel-Layer setzen, falls konfiguriert, sonst wie Quell-Attribut
        if target_layer:
            new_attrib.dxf.layer = target_layer
        elif text_source_attrib.dxf.hasattr('layer'):
            new_attrib.dxf.layer = text_source_attrib.dxf.layer

    return 1, 0, 0


def translate_dxf_texts(input_dxf: Path,
                        output_dxf: Path,
                        translations: dict[str, str],
                        wildcard_patterns: list[str],
                        regex_patterns: list[str],
                        text_source_attr: str = 'TEXT-D',
                        text_target_attr: str = 'TEXT-CS',
                        target_layer: str | None = None):
    """
    Übersetzt alle TEXT, MTEXT Objekte und Text-Attribute in Blöcken einer DXF-Datei.

    Für Block-Attribute:
    - Liest die Übersetzung aus dem Quell-Attribut (z.B. TEXT-D)
    - Schreibt die Übersetzung in das Ziel-Attribut (z.B. TEXT-EN, TEXT-CS) (erstellt das Attribut falls nicht vorhanden)
    - Das Quell-Attribut bleibt unverändert (Original wird bewahrt)

    Args:
        input_dxf: Pfad zur Quell-DXF
        output_dxf: Pfad zur Ziel-DXF
        translations: Dictionary mit source -> target Mappings
        wildcard_patterns: Wildcard-Muster zum Filtern
        regex_patterns: Regex-Muster zum Filtern
        text_source_attr: Quell-Attributname für Texte in Blöcken (z.B. "TEXT-D", "TEXT-EN"). Standard: "TEXT-D"
        text_target_attr: Ziel-Attributname für Übersetzungen in Blöcken (z.B. "TEXT-EN", "TEXT-CS"). Standard: "TEXT-CS"
    """
    # Attribute die ein Block haben muss, damit das Text-Attribut übersetzt wird
    required_attributes = {'IO', 'ID', 'VERW', 'BEZEICHNUNG', 'KENNZEICHNUNG', text_source_attr}

    try:
        doc = ezdxf.readfile(input_dxf)
        msp = doc.modelspace()

        text_translated = 0
        text_skipped = 0
        text_ignored = 0
        mtext_translated = 0
        mtext_skipped = 0
        mtext_ignored = 0
        block_translated = 0
        block_skipped = 0
        block_ignored = 0

        # Übersetze TEXT Objekte
        for entity in msp.query('TEXT'):
            if entity.dxf.text:
                original_text = entity.dxf.text.strip()

                # Prüfe ob Text ignoriert werden soll
                if should_ignore_text(original_text, wildcard_patterns, regex_patterns):
                    text_ignored += 1
                    continue

                # Suche Übersetzung
                if original_text in translations:
                    entity.dxf.text = translations[original_text]
                    text_translated += 1
                else:
                    text_skipped += 1

        # Übersetze MTEXT Objekte
        for entity in msp.query('MTEXT'):
            if entity.text:
                original_text = entity.text.strip()

                # Prüfe ob Text ignoriert werden soll
                if should_ignore_text(original_text, wildcard_patterns, regex_patterns):
                    mtext_ignored += 1
                    continue

                # Suche Übersetzung
                if original_text in translations:
                    entity.text = translations[original_text]
                    mtext_translated += 1
                else:
                    mtext_skipped += 1

        # Übersetze Text-Attribute in Blöcken
        for entity in msp.query('INSERT'):
            translated, skipped, ignored = translate_block_attribute(
                entity,
                doc,
                translations,
                wildcard_patterns,
                regex_patterns,
                required_attributes,
                text_source_attr,
                text_target_attr,
                target_layer,
            )
            block_translated += translated
            block_skipped += skipped
            block_ignored += ignored

        # Speichere neue DXF
        doc.saveas(output_dxf)

        translated_count = text_translated + mtext_translated + block_translated
        skipped_count = text_skipped + mtext_skipped + block_skipped
        ignored_count = text_ignored + mtext_ignored + block_ignored

        print(f"\nDXF-Übersetzung abgeschlossen:")
        print(f"  TEXT übersetzt: {text_translated}, übersprungen: {text_skipped}, ignoriert: {text_ignored}")
        print(f"  MTEXT übersetzt: {mtext_translated}, übersprungen: {mtext_skipped}, ignoriert: {mtext_ignored}")
        print(f"  {text_target_attr} (Blöcke) erstellt/aktualisiert: {block_translated}, übersprungen: {block_skipped}, ignoriert: {block_ignored}")
        print(f"  Gesamt: {translated_count} übersetzt, {skipped_count} übersprungen, {ignored_count} ignoriert")
        print(f"  Hinweis: Übersetzungen in Blöcken wurden in {text_target_attr} geschrieben, TEXT-D bleibt unverändert")
        print(f"  Ausgabe-Datei: {output_dxf}")

    except Exception as e:
        print(f"Fehler beim Übersetzen der DXF-Datei: {e}")
        sys.exit(1)


def write_texts_to_excel(texts: list[str], output_path: Path,
                         translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str], dict[str, str]] = None):
    """
    Schreibt die Texte in die erste Spalte einer Excel-Datei.
    Optional: Schreibt Übersetzungen in die zweite Spalte.

    Args:
        texts: Liste der zu schreibenden Texte
        output_path: Pfad zur Ausgabe-Excel-Datei
        translation_dicts: Optional: Tuple mit (Multi, Trigramme, Bigramme, Single) für Übersetzungen
    """
    wb = Workbook()
    ws = wb.active
    ws.title = "Texte"

    # Kopfzeile
    ws['A1'] = "Text"
    if translation_dicts:
        ws['B1'] = "Übersetzung"

    # Texte schreiben
    for idx, text in enumerate(texts, start=2):
        ws[f'A{idx}'] = text

        # Optional: Übersetzung in zweite Spalte
        if translation_dicts:
            multi, trigramme, bigramme, single = translation_dicts
            translated = translate_text(text, multi, trigramme, bigramme, single)
            if translated:  # Nur wenn Übersetzung gefunden wurde
                ws[f'B{idx}'] = translated
            # Sonst bleibt die Zelle leer

    # Spaltenbreiten anpassen
    ws.column_dimensions['A'].width = 50
    if translation_dicts:
        ws.column_dimensions['B'].width = 50

    try:
        wb.save(output_path)
        print(f"Excel-Datei erfolgreich erstellt: {output_path}")
        if translation_dicts:
            print(f"  Mit Übersetzungen in Spalte B")
    except Exception as e:
        print(f"Fehler beim Schreiben der Excel-Datei: {e}")
        sys.exit(1)


def write_texts_to_json(texts: list[str], ignored_texts: list[str], output_path: Path,
                        translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str], dict[str, str]] = None,
                        translation_dicts_all: dict[str, tuple] = None,
                        translation_languages: list[str] = None):
    """
    Schreibt die Texte in eine JSON-Datei.

    Bei einer Sprache (alte Struktur):
    - "translations": Liste von {source, target} für übersetzte Texte
    - "untranslated": Liste von {source, target: ""} für nicht übersetzte Texte
    - "ignored": Liste von Strings für gefilterte/ignorierte Texte

    Bei mehreren Sprachen (hierarchische Struktur):
    - "[EN]": {"translations": [...], "untranslated": [...]}
    - "[FR]": {"translations": [...], "untranslated": [...]}
    - "ignored": Liste von Strings für gefilterte/ignorierte Texte

    Args:
        texts: Liste der akzeptierten Texte
        ignored_texts: Liste der ignorierten Texte
        output_path: Pfad zur Ausgabe-JSON-Datei
        translation_dicts: Optional: Tuple mit (Multi, Trigramme, Bigramme, Single) für eine Sprache (backwards compatibility)
        translation_dicts_all: Optional: Dict mit {LANG: (multi, trigramme, bigramme, single)} für mehrere Sprachen
        translation_languages: Optional: Liste der Sprachen in der Reihenfolge der Verarbeitung
    """
    output_data = {}

    # Mehrsprachige hierarchische Struktur
    if translation_dicts_all and translation_languages and len(translation_languages) > 1:
        for lang in translation_languages:
            if lang not in translation_dicts_all:
                continue

            multi, trigramme, bigramme, single = translation_dicts_all[lang]
            translations = []
            untranslated = []

            for text in texts:
                translated = translate_text(text, multi, trigramme, bigramme, single)
                if translated:  # Übersetzung vorhanden
                    translations.append({
                        "source": text,
                        "target": translated
                    })
                else:  # Keine Übersetzung vorhanden
                    untranslated.append({
                        "source": text,
                        "target": ""
                    })

            # Hierarchische Struktur: [LANG] als Schlüssel
            output_data[f"[{lang}]"] = {
                "translations": translations,
                "untranslated": untranslated
            }

        # Ignorierte Texte am Ende (ohne Sprachzuordnung)
        output_data["ignored"] = ignored_texts

    # Alte Struktur (eine Sprache oder keine Übersetzung)
    else:
        # Block 1: Übersetzte Texte (als Liste von Dicts mit source/target)
        # Block 2: Nicht übersetzte Texte (als Liste von Dicts mit source und leerem target)
        if translation_dicts:
            multi, trigramme, bigramme, single = translation_dicts
            translations = []
            untranslated = []

            for text in texts:
                translated = translate_text(text, multi, trigramme, bigramme, single)
                if translated:  # Übersetzung vorhanden
                    translations.append({
                        "source": text,
                        "target": translated
                    })
                else:  # Keine Übersetzung vorhanden
                    untranslated.append({
                        "source": text,
                        "target": ""
                    })

            output_data["translations"] = translations
            output_data["untranslated"] = untranslated
        else:
            # Ohne Übersetzungs-Config: alle akzeptierten Texte sind nicht übersetzt
            output_data["translations"] = []
            output_data["untranslated"] = [{"source": text, "target": ""} for text in texts]

        # Block 3: Ignorierte Texte (als Liste)
        output_data["ignored"] = ignored_texts

    try:
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(output_data, f, ensure_ascii=False, indent=2)

        print(f"JSON-Datei erfolgreich erstellt: {output_path}")

        # Ausgabe-Statistiken
        if translation_dicts_all and translation_languages and len(translation_languages) > 1:
            # Mehrsprachige Struktur
            for lang in translation_languages:
                if f"[{lang}]" in output_data:
                    lang_data = output_data[f"[{lang}]"]
                    print(f"  [{lang}] Übersetzungen: {len(lang_data['translations'])}, Nicht übersetzt: {len(lang_data['untranslated'])}")
        else:
            # Alte Struktur
            if translation_dicts:
                print(f"  Übersetzungen: {len(output_data['translations'])}")
                print(f"  Nicht übersetzte Texte: {len(output_data['untranslated'])}")
            else:
                print(f"  Nicht übersetzte Texte: {len(output_data['untranslated'])}")

        print(f"  Ignorierte Texte: {len(ignored_texts)}")
    except Exception as e:
        print(f"Fehler beim Schreiben der JSON-Datei: {e}")
        sys.exit(1)


def write_texts_to_text(texts: list[str], ignored_texts: list[str], output_path: Path,
                        translation_dicts: tuple[dict[str, str], dict[str, str], dict[str, str], dict[str, str]] = None):
    """
    Schreibt die Texte in eine Textdatei mit drei Abschnitten.

    Format:
    --- translations:

    source1 -> target1
    source2 -> target2

    --- untranslated:

    text1
    text2

    --- ignored:

    ignored1
    ignored2

    Args:
        texts: Liste der akzeptierten Texte
        ignored_texts: Liste der ignorierten Texte
        output_path: Pfad zur Ausgabe-Textdatei
        translation_dicts: Optional: Tuple mit (Multi, Trigramme, Bigramme, Single) für Übersetzungen
    """
    try:
        with open(output_path, 'w', encoding='utf-8') as f:
            # Block 1: Übersetzte Texte
            if translation_dicts:
                multi, trigramme, bigramme, single = translation_dicts
                translations = []
                untranslated = []

                for text in texts:
                    translated = translate_text(text, multi, trigramme, bigramme, single)
                    if translated:
                        translations.append((text, translated))
                    else:
                        untranslated.append(text)

                # Schreibe translations Block
                f.write("--- translations:\n\n")
                for source, target in translations:
                    f.write(f"{source} -> {target}\n")

                # Schreibe untranslated Block
                f.write("\n--- untranslated:\n\n")
                for text in untranslated:
                    f.write(f"{text}\n")

            else:
                # Ohne Übersetzungs-Config: alle Texte sind nicht übersetzt
                f.write("--- translations:\n\n")
                f.write("\n--- untranslated:\n\n")
                for text in texts:
                    f.write(f"{text}\n")

            # Block 3: Ignorierte Texte
            f.write("\n--- ignored:\n\n")
            for text in ignored_texts:
                f.write(f"{text}\n")

        print(f"Text-Datei erfolgreich erstellt: {output_path}")
        if translation_dicts:
            translations_count = sum(1 for text in texts if translate_text(text, multi, trigramme, bigramme, single))
            untranslated_count = len(texts) - translations_count
            print(f"  Übersetzungen: {translations_count}")
            print(f"  Nicht übersetzte Texte: {untranslated_count}")
        else:
            print(f"  Nicht übersetzte Texte: {len(texts)}")
        print(f"  Ignorierte Texte: {len(ignored_texts)}")

    except Exception as e:
        print(f"Fehler beim Schreiben der Text-Datei: {e}")
        sys.exit(1)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='Extrahiert TEXT/MTEXT aus DXF, filtert Texte aus TXT, verarbeitet JSON, oder übersetzt DXF direkt. Optional mit Übersetzungsfunktion.',
        prog='translate'
    )

    # Mutually exclusive group: entweder DXF-Extraktion, TXT-Filterung, JSON-Reprocessing oder DXF-zu-DXF
    input_group = parser.add_mutually_exclusive_group(required=True)
    input_group.add_argument(
        '-e', '--extract',
        action='store_true',
        help='Extrahiert Texte aus DXF/DWG Datei (benötigt --filename)'
    )
    input_group.add_argument(
        '--fromtxt',
        action='store',
        metavar='FILE.txt',
        help='Filtert Texte aus einer TXT Datei (ein Text pro Zeile)'
    )
    input_group.add_argument(
        '--fromjson',
        action='store',
        metavar='FILE.json',
        help='Liest untranslated Block aus JSON-Datei und übersetzt erneut (benötigt --translate)'
    )
    input_group.add_argument(
        '--todxf',
        action='store',
        metavar='OUTPUT.dxf',
        help='Übersetzt TEXT/MTEXT in DXF und schreibt neue DXF-Datei (benötigt --filename)'
    )

    parser.add_argument(
        '-f', '--filename',
        action='store',
        help='DXF/DWG Datei (.dxf, .dwg) die verarbeitet werden soll (nur mit --extract)',
        metavar='myfile.dxf'
    )
    parser.add_argument(
        '-o', '--outname',
        action='store',
        help='Name der Ausgabedatei (ohne Pfad, mit oder ohne .xlsx/.txt Endung)',
        metavar='output.xlsx'
    )
    parser.add_argument(
        '-d', '--debug',
        action='store_true',
        help='Aktiviert Debug-Ausgabe für Pattern-Matching'
    )
    parser.add_argument(
        '-t', '--export-type',
        action='store',
        default='json',
        help='Exportformat(e): json (Standard), excel, text oder komma-separiert (z.B. excel,json,text)'
    )
    parser.add_argument(
        '--translate',
        action='store',
        metavar='LANG',
        help='Übersetzt Texte mit Config aus translation/<LANG>.cfg (z.B. CS, EN, FR). Mehrere Sprachen kommasepariert möglich (z.B. EN,FR,CS)'
    )
    parser.add_argument(
        '--lang_source',
        action='store',
        default='DE',
        metavar='LANG',
        help='Quellsprache für Attribut in Blöcken (z.B. DE->TEXT-D, EN->TEXT-EN, CS->TEXT-CS). Standard: DE'
    )
    parser.add_argument(
        '--translation-json',
        action='store',
        metavar='FILE.json',
        help='JSON-Datei mit Übersetzungen für --todxf (Standard: <filename>_texts.json)'
    )

    args = parser.parse_args()

    # Parse und validiere Export-Typen (komma-separierte Liste)
    valid_export_types = {'excel', 'json', 'text'}
    export_types = [t.strip() for t in args.export_type.split(',')]

    # Validiere jeden Export-Typ
    for export_type in export_types:
        if export_type not in valid_export_types:
            parser.error(f"Ungültiger Export-Typ: '{export_type}'. Erlaubt sind: {', '.join(sorted(valid_export_types))}")

    # Entferne Duplikate und behalte Reihenfolge
    seen = set()
    export_types = [t for t in export_types if not (t in seen or seen.add(t))]

    # Validierung: --extract benötigt --filename
    if args.extract and not args.filename:
        parser.error("--extract benötigt --filename")

    # Validierung: --filename nur mit --extract oder --todxf
    if args.filename and not (args.extract or args.todxf):
        parser.error("--filename kann nur mit --extract oder --todxf verwendet werden")

    # Validierung: --fromjson benötigt --translate
    if args.fromjson and not args.translate:
        parser.error("--fromjson benötigt --translate (Übersetzung ist erforderlich)")

    # Validierung: --todxf benötigt --filename
    if args.todxf and not args.filename:
        parser.error("--todxf benötigt --filename (Quell-DXF muss angegeben werden)")

    # Validierung: --translation-json nur mit --todxf
    if args.translation_json and not args.todxf:
        parser.error("--translation-json kann nur mit --todxf verwendet werden")

    # Prüfe Umgebungsvariablen
    work_dir = check_environment_var('PROJECT_WORK')
    cfg_dir = check_environment_var('PROJECT_CFG')

    # Lade Ignore-Muster aus Konfigurationsdatei
    wildcard_patterns, regex_patterns = load_ignore_patterns(cfg_dir)

    # Lade Symbol-Attribut- und Layer-Mapping
    symbol_attr_mapping = load_symbol_attribute_mapping(cfg_dir)
    symbol_layer_mapping = load_symbol_layer_mapping(cfg_dir)

    # Parse Sprachen aus --translate Parameter (kommaseparierte Liste)
    translation_languages = []
    if args.translate:
        translation_languages = [lang.strip().upper() for lang in args.translate.split(',')]
        # Entferne Duplikate und behalte Reihenfolge
        seen = set()
        translation_languages = [lang for lang in translation_languages if lang and not (lang in seen or seen.add(lang))]

    # Bestimme Quell-Attributnamen basierend auf Sprachcode
    source_attr_name = symbol_attr_mapping.get(args.lang_source, 'TEXT-D')

    # Für --todxf: verwende nur die erste Sprache als Ziel (backwards compatibility)
    target_attr_name = symbol_attr_mapping.get(translation_languages[0], 'TEXT-CS') if translation_languages else 'TEXT-CS'
    target_layer_name = symbol_layer_mapping.get(translation_languages[0]) if translation_languages else None

    print(f"\nVerwendete Attribut-Namen:")
    print(f"  Quell-Attribut (--lang_source {args.lang_source}): {source_attr_name}")
    if translation_languages:
        if len(translation_languages) == 1:
            print(f"  Ziel-Attribut (--translate {translation_languages[0]}): {target_attr_name}")
            if target_layer_name:
                print(f"  Ziel-Layer   (--translate {translation_languages[0]}): {target_layer_name}")
        else:
            print(f"  Ziel-Sprachen (--translate): {', '.join(translation_languages)}")

    # Optional: Lade Übersetzungs-Configs für alle Sprachen
    # translation_dicts_all ist ein Dict: {LANG: (multi, trigramme, bigramme, single), ...}
    translation_dicts_all = {}
    if translation_languages:
        translation_dir = check_environment_var('PROJECT_TRANSLATION')
        for lang in translation_languages:
            translation_dicts_all[lang] = load_translation_config(lang, translation_dir)

    # Für backwards compatibility: wenn nur eine Sprache, setze translation_dicts wie früher
    translation_dicts = translation_dicts_all.get(translation_languages[0]) if len(translation_languages) == 1 else None

    if args.debug:
        print("DEBUG-Modus aktiviert")

    # Spezieller Workflow: DXF-zu-DXF Übersetzung
    if args.todxf:
        print("=== DXF-zu-DXF Übersetzung ===\n")

        # Finde Quell-DXF
        input_filename = args.filename
        if not input_filename.lower().endswith(('.dxf', '.dwg')):
            print("Mit --todxf nur .dxf oder .dwg Dateien als Quelle erlaubt")
            sys.exit(1)

        source_dxf = work_dir / input_filename
        if not source_dxf.exists():
            source_dxf = Path(input_filename)
            if not source_dxf.exists():
                print(f"Quell-DXF nicht gefunden: {input_filename}")
                print(f"  Geprüft in Work-Ordner: {work_dir / input_filename}")
                print(f"  Geprüft als vollständiger Pfad: {source_dxf}")
                sys.exit(1)

        print(f"Quell-DXF: {source_dxf}")

        # Bestimme JSON-Datei mit Übersetzungen
        if args.translation_json:
            translation_json = work_dir / args.translation_json
            if not translation_json.exists():
                translation_json = Path(args.translation_json)
                if not translation_json.exists():
                    print(f"Übersetzungs-JSON nicht gefunden: {args.translation_json}")
                    print(f"  Geprüft in Work-Ordner: {work_dir / args.translation_json}")
                    print(f"  Geprüft als vollständiger Pfad: {translation_json}")
                    sys.exit(1)
        else:
            # Standard: <filename>_texts.json im Work-Ordner
            translation_json = work_dir / (Path(input_filename).stem + "_texts.json")
            if not translation_json.exists():
                print(f"Standard-Übersetzungs-JSON nicht gefunden: {translation_json}")
                print(f"Bitte erstellen Sie die Datei oder geben Sie eine alternative mit --translation-json an")
                sys.exit(1)

        print(f"Übersetzungs-JSON: {translation_json}")

        # Lade Übersetzungen (verwende erste Sprache als Ziel-Sprache)
        target_lang = translation_languages[0] if translation_languages else None
        translations = load_translations_from_json(translation_json, target_lang)

        # Bestimme Ausgabe-DXF
        output_dxf_name = args.todxf
        if not output_dxf_name.lower().endswith(('.dxf', '.dwg')):
            output_dxf_name += '.dxf'
        output_dxf = work_dir / output_dxf_name

        print(f"Ziel-DXF: {output_dxf}")

        # Übersetze DXF (inkl. ggf. neuer TEXT-Attribute und Layer)
        translate_dxf_texts(
            source_dxf,
            output_dxf,
            translations,
            wildcard_patterns,
            regex_patterns,
            source_attr_name,
            target_attr_name,
            target_layer_name,
        )

        print("\nVerarbeitung erfolgreich abgeschlossen.")
        sys.exit(0)

    # Bestimme Input-Modus und Datei
    if args.extract:
        # DXF/DWG-Modus
        input_filename = args.filename
        file_extension = input_filename.lower()

        if not file_extension.endswith(('.dxf', '.dwg')):
            print("Mit --extract nur .dxf oder .dwg Dateien erlaubt")
            sys.exit(1)

        # Versuche zuerst Datei im Work-Ordner zu finden
        filename = work_dir / input_filename
        if not filename.exists():
            # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist
            filename = Path(input_filename)
            if not filename.exists():
                print(f"Datei nicht gefunden: {input_filename}")
                print(f"  Geprüft in Work-Ordner: {work_dir / input_filename}")
                print(f"  Geprüft als vollständiger Pfad: {filename}")
                sys.exit(1)

        print(f"Lese DXF Datei: {filename}")
        all_texts = extract_text_from_dxf(filename, source_attr_name)

    elif args.fromtxt:
        # TXT-Modus
        input_filename = args.fromtxt

        if not input_filename.lower().endswith('.txt'):
            print("Mit --fromtxt nur .txt Dateien erlaubt")
            sys.exit(1)

        # Versuche zuerst Datei im Work-Ordner zu finden
        filename = work_dir / input_filename
        if not filename.exists():
            # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist
            filename = Path(input_filename)
            if not filename.exists():
                print(f"Datei nicht gefunden: {input_filename}")
                print(f"  Geprüft in Work-Ordner: {work_dir / input_filename}")
                print(f"  Geprüft als vollständiger Pfad: {filename}")
                sys.exit(1)

        print(f"Lese Textdatei: {filename}")
        all_texts = read_texts_from_file(filename)

    else:  # args.fromjson
        # JSON-Modus
        input_filename = args.fromjson

        if not input_filename.lower().endswith('.json'):
            print("Mit --fromjson nur .json Dateien erlaubt")
            sys.exit(1)

        # Versuche zuerst Datei im Work-Ordner zu finden
        filename = work_dir / input_filename
        if not filename.exists():
            # Falls nicht gefunden, prüfe ob es ein vollständiger Pfad ist
            filename = Path(input_filename)
            if not filename.exists():
                print(f"Datei nicht gefunden: {input_filename}")
                print(f"  Geprüft in Work-Ordner: {work_dir / input_filename}")
                print(f"  Geprüft als vollständiger Pfad: {filename}")
                sys.exit(1)

        print(f"Lese JSON-Datei: {filename}")
        all_texts = read_texts_from_json(filename)

    # Schritt 2: Texte filtern
    texts, ignored_texts = filter_texts(all_texts, wildcard_patterns, regex_patterns, args.debug)

    # Mapping für Dateiendungen
    extension_map = {
        'excel': '.xlsx',
        'json': '.json',
        'text': '.txt'
    }

    # Export für jeden angegebenen Typ durchführen
    print(f"\nExportiere in {len(export_types)} Format(e): {', '.join(export_types)}")

    for export_type in export_types:
        # Erstelle Output-Dateinamen basierend auf Export-Typ
        if args.outname:
            # Verwende benutzerdefinierten Namen
            base_name = args.outname
            # Entferne existierende Endung falls vorhanden
            for ext in extension_map.values():
                if base_name.endswith(ext):
                    base_name = base_name[:-len(ext)]
                    break
            # Füge korrekte Endung hinzu
            output_filename = f"{base_name}{extension_map[export_type]}"
        else:
            # Standardname basierend auf Eingabedatei
            output_filename = f"{filename.stem}_texts{extension_map[export_type]}"

        output_path = work_dir / output_filename

        # Schreibe Texte in gewähltem Format
        if export_type == 'excel':
            write_texts_to_excel(texts, output_path, translation_dicts)
        elif export_type == 'json':
            write_texts_to_json(texts, ignored_texts, output_path, translation_dicts, translation_dicts_all, translation_languages)
        else:  # text
            write_texts_to_text(texts, ignored_texts, output_path, translation_dicts)

        print(f"  [OK] {export_type}: {output_path}")

    print("\nVerarbeitung erfolgreich abgeschlossen.")
