import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import xml.etree.ElementTree as ET
import fitz  # PyMuPDF
import math
import os
import statistics

# ==========================================
# 1. FEINTUNING (Der "kleine Versatz" für das Label)
NUDGE_X = 3.0
NUDGE_Y = 3.0

# 2. ZOOM FEINTUNING (Zentrierung beim Klick)
ZOOM_FACTOR = 2.5       # 1.5 = 150% Zoom (0.0 = Zoom des Users beibehalten)
ZOOM_OFFSET_X = 0.0    # Wie weit links vom Bauteil soll der Bildschirmrand sein?
ZOOM_OFFSET_Y = 0.0    # Wie weit über dem Bauteil soll der Bildschirmrand sein? (Verhindert halbe Seiten!)

# 3. XREF: SLAVE EINSTELLUNGEN
XREF_SLAVE_OFFSET_BOTTOM_Y = 21.0
XREF_SLAVE_OFFSET_RIGHT_X = 27.0
XREF_SLAVE_OFFSET_LEFT_X = -28.0
XREF_SLAVE_OFFSET_TOP_Y = -19.0

# 4. XREF: MASTER EINSTELLUNGEN (Coil / Protection)
XREF_XML_OFFSET_SCALE = 0.4      # Halbiert den Start-Versatz (offset="40") aus der XML

XREF_MASTER_OFFSET_X = 28.0       # Genereller Versatz nach rechts in den Kamm (um 1 Punkt erhöht)
XREF_MASTER_NO_NC_STEP_Y = 10.0   # Zeilenabstand für normale Öffner/Schließer

XREF_MASTER_SW_OFFSET_Y_EXTRA = 3.0 # Wechsler Start
XREF_MASTER_SW_STEP_Y = 20.0        # Wechsler: Braucht mehr Platz bis zum NÄCHSTEN Kontakt

# 5. XREF: COMMUTATOR EINSTELLUNGEN (Eigene Logik)
XREF_COMMUTATOR_OFFSET_X = 28.0     # Eigener Rechtsversatz
XREF_COMMUTATOR_OFFSET_Y_START = -5.0 # Eigener Startwert
XREF_COMMUTATOR_STEP_Y = 12.0       # Eigener Zeilenabstand

# ==========================================
# SPRACH-ÜBERSETZUNGEN (GUI & INFO-BOXEN)
# ==========================================
GUI_TEXTS = {
    "Deutsch": {
        "qet_label": "1. QET Projektdatei:",
        "pdf_label": "2. PDF Datei:",
        "btn_browse": "Suche",
        "frame_opts": "Optionen",
        "chk_debug": "Rote Rahmen zur Kontrolle einzeichnen",
        "chk_info": "Bauteilinformationen (Info-Boxen) eintragen",
        "chk_terminals": "Klemmen von Info-Boxen ausschließen",
        "chk_icons": "Info-Symbole (gelbe Icons) sichtbar machen (für Debug)",
        "btn_generate": "Links & Infos generieren",
        "msg_start_calib": "Starte globale optische Kalibrierung...",
        "msg_done": "FERTIG!\n- {} dynamische Links erstellt.",
        "msg_done_info": "\n- {} Bauteil-Info-Boxen gesetzt.",
        "msg_success_title": "Erfolg",
        "msg_success_text": "Datei gespeichert:\n{}\n\n{}",
        "msg_error_title": "Fehler"
    },
    "English": {
        "qet_label": "1. QET Project file:",
        "pdf_label": "2. PDF File:",
        "btn_browse": "Browse",
        "frame_opts": "Options",
        "chk_debug": "Draw red frames for debugging",
        "chk_info": "Insert component info (Info-Boxes)",
        "chk_terminals": "Exclude terminals from Info-Boxes",
        "chk_icons": "Make info symbols (yellow icons) visible (for debug)",
        "btn_generate": "Generate Links & Infos",
        "msg_start_calib": "Starting global optical calibration...",
        "msg_done": "DONE!\n- {} dynamic links created.",
        "msg_done_info": "\n- {} component Info-Boxes inserted.",
        "msg_success_title": "Success",
        "msg_success_text": "File saved:\n{}\n\n{}",
        "msg_error_title": "Error"
    }
}

INFO_TRANSLATIONS = {
    "Deutsch": {
        'label': 'BMK', 'plant': 'Anlage', 'location': 'Ort', 'comment': 'Kommentar',
        'function': 'Funktion', 'description': 'Artikelbeschreibung', 'designation': 'Artikelnummer',
        'manufacturer': 'Hersteller', 'manufacturer_reference': 'Bestellnummer',
        'machine_manufacturer_reference': 'Interne Nummer', 'supplier': 'Lieferant',
        'quantity': 'Menge', 'unity': 'Einheit',
        'position': 'Position', 'title': 'Titel', 'folio': 'Foliennummer', 'diagram_position': 'Seite'
    },
    "English": {
        'label': 'Label', 'plant': 'Plant', 'location': 'Location', 'comment': 'Comment',
        'function': 'Function', 'description': 'Description', 'designation': 'Part number',
        'manufacturer': 'Manufacturer', 'manufacturer_reference': 'Order number',
        'machine_manufacturer_reference': 'Internal number', 'supplier': 'Supplier',
        'quantity': 'Quantity', 'unity': 'Unit',
        'position': 'Position', 'title': 'Title', 'folio': 'Folio', 'diagram_position': 'Page'
    }
}

# Zusatzartikel 1-4 dynamisch generieren
for i in range(1, 5):
    INFO_TRANSLATIONS["Deutsch"].update({
        f'auxiliary{i}': f'Zusatzinfo Zusatzartikel {i}',
        f'description_auxiliary{i}': f'Artikelbeschreibung Zusatzartikel {i}',
        f'designation_auxiliary{i}': f'Artikelnummer Zusatzartikel {i}',
        f'manufacturer_auxiliary{i}': f'Hersteller Zusatzartikel {i}',
        f'manufacturer_reference_auxiliary{i}': f'Bestellnummer Zusatzartikel {i}',
        f'machine_manufacturer_reference_auxiliary{i}': f'Interne Nummer Zusatzartikel {i}',
        f'supplier_auxiliary{i}': f'Lieferant Zusatzartikel {i}',
        f'quantity_auxiliary{i}': f'Menge Zusatzartikel {i}',
        f'unity_auxiliary{i}': f'Einheit Zusatzartikel {i}'
    })
    INFO_TRANSLATIONS["English"].update({
        f'auxiliary{i}': f'Auxiliary info {i}',
        f'description_auxiliary{i}': f'Description {i}',
        f'designation_auxiliary{i}': f'Part number {i}',
        f'manufacturer_auxiliary{i}': f'Manufacturer {i}',
        f'manufacturer_reference_auxiliary{i}': f'Order number {i}',
        f'machine_manufacturer_reference_auxiliary{i}': f'Internal number {i}',
        f'supplier_auxiliary{i}': f'Supplier {i}',
        f'quantity_auxiliary{i}': f'Quantity {i}',
        f'unity_auxiliary{i}': f'Unit {i}'
    })

# ==========================================

def try_float(v, default=0.0):
    if v is None: return default
    try: return float(str(v).replace(',', '.'))
    except: return default

def norm_uuid(u):
    if not u: return None
    return u.strip('{} ').lower()

class QETSmartLinker:
    def __init__(self, root):
        self.root = root
        self.root.title("QET Xref-Linker v7.1 (Invisible Info-Boxes)")
        self.root.geometry("600x570") # Leicht erhöht für das Sprach-Dropdown

        # --- SPRACHAUSWAHL ---
        top_frm = ttk.Frame(root)
        top_frm.pack(fill=tk.X, padx=20, pady=(10, 0))
        self.lang_var = tk.StringVar(value="Deutsch")
        self.lang_combo = ttk.Combobox(top_frm, textvariable=self.lang_var, values=["Deutsch", "English"], state="readonly", width=10)
        self.lang_combo.pack(side=tk.RIGHT)
        self.lang_combo.bind("<<ComboboxSelected>>", self.update_language)

        frm = ttk.Frame(root, padding="20")
        frm.pack(fill=tk.BOTH, expand=True)

        self.lbl_qet = ttk.Label(frm, text="")
        self.lbl_qet.pack(anchor="w")
        self.ent_qet = ttk.Entry(frm, width=50)
        self.ent_qet.pack(fill="x", pady=5)
        self.btn_qet_browse = ttk.Button(frm, text="", command=self.browse_qet)
        self.btn_qet_browse.pack(anchor="e")

        self.lbl_pdf = ttk.Label(frm, text="")
        self.lbl_pdf.pack(anchor="w", pady=(10,0))
        self.ent_pdf = ttk.Entry(frm, width=50)
        self.ent_pdf.pack(fill="x", pady=5)
        self.btn_pdf_browse = ttk.Button(frm, text="", command=self.browse_pdf)
        self.btn_pdf_browse.pack(anchor="e")

        # --- OPTIONEN ---
        self.frame_opts = ttk.LabelFrame(frm, text="", padding="10")
        self.frame_opts.pack(fill="x", pady=15)

        self.var_debug = tk.BooleanVar(value=True)
        self.chk_debug_btn = ttk.Checkbutton(self.frame_opts, text="", variable=self.var_debug)
        self.chk_debug_btn.pack(anchor="w")

        self.var_info = tk.BooleanVar(value=False)
        self.chk_info = ttk.Checkbutton(self.frame_opts, text="", variable=self.var_info, command=self.toggle_info_options)
        self.chk_info.pack(anchor="w", pady=(10, 0))

        self.var_klemmen = tk.BooleanVar(value=False)
        self.chk_klemmen = ttk.Checkbutton(self.frame_opts, text="", variable=self.var_klemmen, state=tk.DISABLED)
        self.chk_klemmen.pack(anchor="w", padx=20)

        self.var_info_visible = tk.BooleanVar(value=True)
        self.chk_info_visible = ttk.Checkbutton(self.frame_opts, text="", variable=self.var_info_visible, state=tk.DISABLED)
        self.chk_info_visible.pack(anchor="w", padx=20)

        self.btn_generate = ttk.Button(frm, text="", command=self.process)
        self.btn_generate.pack(fill="x", ipady=5, pady=10)

        self.status = tk.Text(frm, height=8, font=("Consolas", 8))
        self.status.pack(fill="x")

        # Initiale Texte setzen
        self.update_language()

    def update_language(self, event=None):
        """Aktualisiert alle GUI-Texte auf Basis der gewählten Sprache."""
        t = GUI_TEXTS[self.lang_var.get()]
        self.lbl_qet.config(text=t["qet_label"])
        self.btn_qet_browse.config(text=t["btn_browse"])
        self.lbl_pdf.config(text=t["pdf_label"])
        self.btn_pdf_browse.config(text=t["btn_browse"])
        self.frame_opts.config(text=t["frame_opts"])
        self.chk_debug_btn.config(text=t["chk_debug"])
        self.chk_info.config(text=t["chk_info"])
        self.chk_klemmen.config(text=t["chk_terminals"])
        self.chk_info_visible.config(text=t["chk_icons"])
        self.btn_generate.config(text=t["btn_generate"])

    def toggle_info_options(self):
        """Aktiviert/Deaktiviert die Unter-Checkboxen für die Info-Box."""
        if self.var_info.get():
            self.chk_klemmen.config(state=tk.NORMAL)
            self.chk_info_visible.config(state=tk.NORMAL)
        else:
            self.chk_klemmen.config(state=tk.DISABLED)
            self.chk_info_visible.config(state=tk.DISABLED)
            self.var_klemmen.set(False)

    def log(self, msg):
        self.status.insert(tk.END, msg + "\n")
        self.status.see(tk.END)
        self.root.update()

    def browse_qet(self):
        f = filedialog.askopenfilename(filetypes=[("QET", "*.qet")])
        if f: self.ent_qet.delete(0, tk.END); self.ent_qet.insert(0, f)

    def browse_pdf(self):
        f = filedialog.askopenfilename(filetypes=[("PDF", "*.pdf")])
        if f: self.ent_pdf.delete(0, tk.END); self.ent_pdf.insert(0, f)

    def get_global_calibration(self, doc, diagrams):
        self.log(GUI_TEXTS[self.lang_var.get()]["msg_start_calib"])
        anchors_x, anchors_y = [], []

        for d_idx, diag in enumerate(diagrams):
            try: page_num = int(diag.get('order', d_idx + 1)) - 1
            except: page_num = d_idx
            if page_num >= len(doc) or page_num < 0: continue
            page = doc[page_num]

            for el in diag.findall('.//element'):
                for dt in el.findall('.//dynamic_text') + el.findall('.//dynamic_elmt_text'):
                    txt_node = dt.find('text')
                    if txt_node is not None and txt_node.text and len(txt_node.text.strip()) > 1:
                        val = txt_node.text.strip()
                        qx = try_float(el.get('x')) + try_float(dt.get('x'))
                        qy = try_float(el.get('y')) + try_float(dt.get('y'))

                        rects = page.search_for(val)
                        if len(rects) == 1:
                            anchors_x.append((qx, rects[0].x0))
                            anchors_y.append((qy, rects[0].y0))

        if len(anchors_x) < 2: return None, None, None, None

        scales_x = [(anchors_x[i][1]-anchors_x[j][1])/(anchors_x[i][0]-anchors_x[j][0]) for i in range(len(anchors_x)) for j in range(i+1, len(anchors_x)) if abs(anchors_x[i][0]-anchors_x[j][0]) > 200]
        scales_y = [(anchors_y[i][1]-anchors_y[j][1])/(anchors_y[i][0]-anchors_y[j][0]) for i in range(len(anchors_y)) for j in range(i+1, len(anchors_y)) if abs(anchors_y[i][0]-anchors_y[j][0]) > 200]

        sc_x = statistics.median(scales_x) if scales_x else 0.72
        sc_y = statistics.median(scales_y) if scales_y else 0.72
        off_x = statistics.median([p - (q * sc_x) for q, p in anchors_x]) if anchors_x else 0.0
        off_y = statistics.median([p - (q * sc_y) for q, p in anchors_y]) if anchors_y else 0.0

        return sc_x, sc_y, off_x, off_y

    def get_best_label(self, element):
        best_dt = None
        for tag in ['.//dynamic_text', './/dynamic_elmt_text']:
            for dt in element.findall(tag):
                info = dt.find('info_name')
                if info is not None and info.text == 'label':
                    tf = dt.get('text_from')
                    if tf == 'ElementInfo': return dt
                    elif tf == 'UserText' and (best_dt is None or best_dt.get('text_from') != 'UserText'): best_dt = dt
                    elif best_dt is None: best_dt = dt
        return best_dt

    def parse_xrefs_config(self, root_xml):
        xref_config = {}
        for xref in root_xml.findall('.//xrefs/xref'):
            t = xref.get('type')
            if t:
                xref_config[t] = {
                    'pos': xref.get('xrefpos', 'AlignBottom'),
                    'offset': try_float(xref.get('offset'), 40.0)
                }
        return xref_config

    def build_link_type_map(self, root_xml):
        l_types = {}
        for c_el in root_xml.findall('.//collection//element'):
            name = c_el.get('name')
            df = c_el.find('definition')
            if name and df is not None:
                l_type = df.get('link_type', 'simple')
                k_type = 'simple'
                c_count = 1
                c_state = 'NO'

                for ki in df.findall('.//kindInformation'):
                    n = ki.get('name')
                    if n == 'type' and ki.text: k_type = ki.text.strip().lower()
                    if n == 'number' and ki.text:
                        try: c_count = int(ki.text.strip())
                        except: pass
                    if n == 'state' and ki.text: c_state = ki.text.strip().upper()

                l_types[name] = {'link_type': l_type, 'kind_type': k_type, 'contact_count': c_count, 'state': c_state}
        return l_types

    def process(self):
        if not self.ent_qet.get() or not self.ent_pdf.get(): return

        try:
            self.status.delete(1.0, tk.END)
            tree = ET.parse(self.ent_qet.get())
            root_xml = tree.getroot()
            doc = fitz.open(self.ent_pdf.get())

            xref_config = self.parse_xrefs_config(root_xml)
            link_type_map = self.build_link_type_map(root_xml)
            element_map = {norm_uuid(el.get('uuid') or el.get('id')): el for el in root_xml.findall('.//element')}
            diagrams = root_xml.findall('.//diagram')

            sc_x, sc_y, off_x, off_y = self.get_global_calibration(doc, diagrams)
            if sc_x is None: return

            links_count = 0
            info_count = 0

            # Wörterbuch für die aktuell gewählte Sprache laden
            current_lang = self.lang_var.get()
            gui_trans = GUI_TEXTS[current_lang]
            info_trans = INFO_TRANSLATIONS[current_lang]

            for d_idx, diag in enumerate(diagrams):
                try: page_num = int(diag.get('order', d_idx + 1)) - 1
                except: page_num = d_idx
                if page_num >= len(doc) or page_num < 0: continue
                page = doc[page_num]

                for el in diag.findall('.//element'):
                    el_type_raw = el.get('type', '')
                    el_name = el_type_raw.split('/')[-1]

                    type_info = link_type_map.get(el_name, {'link_type': 'simple', 'kind_type': 'coil'})
                    l_type = type_info['link_type']
                    k_type = type_info['kind_type']

                    # --- KOORDINATEN FÜR DAS LABEL (Für Links) ---
                    best_dt = self.get_best_label(el)
                    tx, ty = 0.0, 0.0
                    label_text = None

                    if best_dt is not None:
                        tx, ty = try_float(best_dt.get('x')), try_float(best_dt.get('y'))
                        t_node = best_dt.find('text')
                        if t_node is not None and t_node.text:
                            label_text = t_node.text.strip()

                    orient_step = try_float(el.get('orientation'), 0.0)
                    angle_deg = orient_step * 90.0

                    if angle_deg != 0:
                        rad = math.radians(angle_deg)
                        rx = tx * math.cos(rad) - ty * math.sin(rad)
                        ry = tx * math.sin(rad) + ty * math.cos(rad)
                        tx, ty = rx, ry

                    # Koordinaten des Labels
                    pdf_x = off_x + (try_float(el.get('x')) + tx) * sc_x + NUDGE_X
                    pdf_y = off_y + (try_float(el.get('y')) + ty) * sc_y + NUDGE_Y

                    if label_text:
                        found_rects = page.search_for(label_text)
                        if found_rects:
                            best_rect = None
                            min_dist = float('inf')
                            for r in found_rects:
                                cx, cy = (r.x0 + r.x1)/2, (r.y0 + r.y1)/2
                                dist = math.hypot(cx - pdf_x, cy - pdf_y)
                                if dist < 80 and dist < min_dist:
                                    min_dist = dist
                                    best_rect = r
                            if best_rect:
                                pdf_x = (best_rect.x0 + best_rect.x1) / 2
                                pdf_y = (best_rect.y0 + best_rect.y1) / 2

                    # ==================================================
                    # NEU: INFO-BOXEN (Tooltips direkt auf dem Bauteil)
                    # ==================================================
                    if self.var_info.get():
                        is_arrow = l_type in ['next_report', 'previous_report']
                        is_slave = (l_type == 'slave')
                        is_terminal = (l_type == 'terminal')
                        skip_terminal = is_terminal and self.var_klemmen.get()

                        if not (is_arrow or is_slave or skip_terminal):
                            info_data = {}
                            # Lese alle elementInformations aus
                            for e_info in el.findall('.//elementInformations/elementInformation'):
                                name = e_info.get('name')
                                val = e_info.text

                                # formula hart filtern!
                                if name == 'formula':
                                    continue

                                if name and val and str(e_info.get('show', '1')) == '1':
                                    info_data[name] = val.strip()

                            if info_data:
                                info_lines = []
                                # Prio-Felder ganz oben auflisten
                                for key in ['label', 'manufacturer', 'designation', 'description']:
                                    if key in info_data:
                                        info_lines.append(f"{info_trans.get(key, key)}: {info_data.pop(key)}")

                                # Den Rest anhängen
                                for key, val in info_data.items():
                                    info_lines.append(f"{info_trans.get(key, key)}: {val}")

                                info_text = "\n".join(info_lines)

                                # --- NEUE LOGIK: OBERSTER & LINKESTER ANSCHLUSS ---
                                terminals = el.findall('.//terminal')

                                if terminals:
                                    abs_t_x = []
                                    abs_t_y = []
                                    for t in terminals:
                                        t_x = try_float(t.get('x'))
                                        t_y = try_float(t.get('y'))

                                        # Drehung des Bauteils auf die Klemme anwenden
                                        if angle_deg != 0:
                                            rad_t = math.radians(angle_deg)
                                            rx_t = t_x * math.cos(rad_t) - t_y * math.sin(rad_t)
                                            ry_t = t_x * math.sin(rad_t) + t_y * math.cos(rad_t)
                                            t_x, t_y = rx_t, ry_t

                                        abs_t_x.append(try_float(el.get('x')) + t_x)
                                        abs_t_y.append(try_float(el.get('y')) + t_y)

                                    # Den am weitesten linken und obersten Punkt nehmen
                                    target_x = min(abs_t_x)
                                    target_y = min(abs_t_y)
                                else:
                                    # Fallback, falls das Bauteil gar keine Anschlüsse hat: Einfügepunkt
                                    target_x = try_float(el.get('x'))
                                    target_y = try_float(el.get('y'))

                                # PDF Koordinaten berechnen
                                # Versatz nach links/oben um ca. 12 Punkt (halbe Icon-Größe),
                                # damit die Mitte des Icons exakt auf der Klemme liegt.
                                base_pdf_x = off_x + (target_x * sc_x) - 12.0
                                base_pdf_y = off_y + (target_y * sc_y) - 12.0

                                annot_pt = fitz.Point(base_pdf_x, base_pdf_y)
                                annot = page.add_text_annot(annot_pt, info_text)
                                annot.set_info(title=str(info_data.get('label', 'Bauteil-Info')))

                                # Unsichtbar machen, wenn Debug aus ist
                                if not self.var_info_visible.get():
                                    annot.set_opacity(0.0)

                                annot.update()
                                info_count += 1
                    # ==================================================

                    text_rot = try_float(best_dt.get('rotation'), 0.0) if best_dt is not None else 0.0
                    total_rot = (angle_deg + text_rot) % 180
                    is_rotated = (total_rot == 90.0)
                    w_off = 3.0 if is_rotated else 6.0
                    h_off = 6.0 if is_rotated else 3.0

                    config = xref_config.get(k_type, {'offset': 40.0})
                    current_y_offset = config['offset'] * XREF_XML_OFFSET_SCALE

                    # --- VERLINKUNG ---
                    tuids = [norm_uuid(l.get('uuid')) for l in el.findall('.//link_uuid') if l.get('uuid')]
                    if not tuids: continue

                    for t_uid in tuids:
                        target_el = element_map.get(t_uid)
                        if target_el is None: continue # <--- HIER WURDE DIE WARNUNG BEHOBEN

                        tp_num = -1
                        t_pdf_x, t_pdf_y = 0.0, 0.0
                        for td_idx, tdiag in enumerate(diagrams):
                            if target_el in tdiag.findall('.//element'):
                                try: tp_num = int(tdiag.get('order', td_idx + 1)) - 1
                                except: tp_num = td_idx
                                t_pdf_x = off_x + try_float(target_el.get('x')) * sc_x
                                t_pdf_y = off_y + try_float(target_el.get('y')) * sc_y
                                break

                        if tp_num == -1: continue

                        # --- SMARTE ZENTRIERUNG FÜR DEN ZOOM ---
                        if ZOOM_OFFSET_X == 0.0 and ZOOM_OFFSET_Y == 0.0 and ZOOM_FACTOR > 0:
                            smart_offset_x = 800.0 / ZOOM_FACTOR
                            smart_offset_y = 450.0 / ZOOM_FACTOR
                            target_view_x = max(0, t_pdf_x - smart_offset_x)
                            target_view_y = max(0, t_pdf_y - smart_offset_y)
                        else:
                            target_view_x = max(0, t_pdf_x - (ZOOM_OFFSET_X * sc_x))
                            target_view_y = max(0, t_pdf_y - (ZOOM_OFFSET_Y * sc_y))

                        link_dest = {
                            'kind': fitz.LINK_GOTO,
                            'page': tp_num,
                            'to': fitz.Point(target_view_x, target_view_y),
                            'zoom': ZOOM_FACTOR
                        }

                        t_el_type_raw = target_el.get('type', '')
                        t_el_name = t_el_type_raw.split('/')[-1]
                        t_type_info = link_type_map.get(t_el_name, {'contact_count': 1, 'state': 'NO', 'kind_type': 'coil'})

                        c_count = t_type_info['contact_count']
                        t_state = t_type_info['state']
                        target_k_type = t_type_info['kind_type']

                        if l_type in ['next_report', 'previous_report']:
                            rect = fitz.Rect(pdf_x - w_off, pdf_y - h_off, pdf_x + w_off, pdf_y + h_off)
                            link_dest['from'] = rect
                            page.insert_link(link_dest)
                            if self.var_debug.get(): page.draw_rect(rect, color=(1,0,0), width=0.5)
                            links_count += 1

                        elif l_type == 'slave':
                            target_config = xref_config.get(target_k_type, {'pos': 'AlignBottom', 'offset': 40.0})

                            if target_config['pos'] == 'AlignRight':
                                rx = pdf_x + (XREF_SLAVE_OFFSET_RIGHT_X * sc_x)
                                ry = pdf_y
                            elif target_config['pos'] == 'AlignLeft':
                                rx = pdf_x + (XREF_SLAVE_OFFSET_LEFT_X * sc_x)
                                ry = pdf_y
                            elif target_config['pos'] == 'AlignTop':
                                rx = pdf_x
                                ry = pdf_y + (XREF_SLAVE_OFFSET_TOP_Y * sc_y)
                            else:
                                rx = pdf_x
                                ry = pdf_y + (XREF_SLAVE_OFFSET_BOTTOM_Y * sc_y)

                            rect = fitz.Rect(rx - 6, ry - 3, rx + 6, ry + 3)
                            link_dest['from'] = rect
                            page.insert_link(link_dest)
                            if self.var_debug.get(): page.draw_rect(rect, color=(1,0,0), width=0.5)
                            links_count += 1

                        elif l_type == 'master':
                            if k_type == 'commutator':
                                for _ in range(c_count):
                                    rx = pdf_x + (XREF_COMMUTATOR_OFFSET_X * sc_x)
                                    ry = pdf_y + ((XREF_COMMUTATOR_OFFSET_Y_START + current_y_offset) * sc_y)

                                    rect = fitz.Rect(rx - 6, ry - 3, rx + 6, ry + 3)
                                    link_dest['from'] = rect
                                    page.insert_link(link_dest)
                                    if self.var_debug.get(): page.draw_rect(rect, color=(1,0,0), width=0.5)
                                    links_count += 1

                                    current_y_offset += XREF_COMMUTATOR_STEP_Y
                            else:
                                if t_state == 'SW':
                                    rx = pdf_x + (XREF_MASTER_OFFSET_X * sc_x)
                                    ry = pdf_y + ((current_y_offset + XREF_MASTER_SW_OFFSET_Y_EXTRA) * sc_y)

                                    rect = fitz.Rect(rx - 6, ry - 3, rx + 6, ry + 3)
                                    link_dest['from'] = rect
                                    page.insert_link(link_dest)
                                    if self.var_debug.get(): page.draw_rect(rect, color=(1,0,0), width=0.5)
                                    links_count += 1

                                    current_y_offset += XREF_MASTER_SW_STEP_Y
                                else:
                                    for _ in range(c_count):
                                        rx = pdf_x + (XREF_MASTER_OFFSET_X * sc_x)
                                        ry = pdf_y + (current_y_offset * sc_y)

                                        rect = fitz.Rect(rx - 6, ry - 3, rx + 6, ry + 3)
                                        link_dest['from'] = rect
                                        page.insert_link(link_dest)
                                        if self.var_debug.get(): page.draw_rect(rect, color=(1,0,0), width=0.5)
                                        links_count += 1

                                        current_y_offset += XREF_MASTER_NO_NC_STEP_Y

            out_path = self.ent_pdf.get().replace(".pdf", "_linked.pdf")
            doc.save(out_path); doc.close()

            # Erfolgsmeldung in der jeweiligen Sprache
            msg_done_base = gui_trans["msg_done"].format(links_count)
            if self.var_info.get():
                msg_done_base += gui_trans["msg_done_info"].format(info_count)
            self.log(msg_done_base)
            messagebox.showinfo(gui_trans["msg_success_title"], gui_trans["msg_success_text"].format(os.path.basename(out_path), msg_done_base))

        except Exception as e:
            self.log(f"FEHLER: {e}")

            # Falls die Sprache noch nicht geladen ist, Standard-String nehmen
            err_title = GUI_TEXTS.get(getattr(self, 'lang_var', tk.StringVar(value="Deutsch")).get(), GUI_TEXTS["Deutsch"])["msg_error_title"]
            messagebox.showerror(err_title, str(e))

if __name__ == "__main__":
    root = tk.Tk(); app = QETSmartLinker(root); root.mainloop()
