#!/usr/bin/env python3
# Klemmenplan_qet.py
import sys
import xml.etree.ElementTree as ET
import re
import copy
import traceback
from xml.sax.saxutils import escape
# Importiere die CSV-Export-Funktion und wandle sie in einen Row-Generator um
from Klemmplan_csv import process_file
import os, csv, tempfile, io

# --------------------
# Konfiguration / Layout
# --------------------
PEN_SHAPE = {'widthF':'1','style':'SolidLine','color':'#000000'}
BRUSH_HEADER_GRAY = {'style':'SolidPattern','color':'#c0c0c0'}
BRUSH_NO = {'style':'NoBrush','color':'#000000'}
FONT_HEADER = "Noto Sans,9,-1,5,50,0,0,0,0,0"
FONT_ROW    = "Noto Sans,8,-1,5,50,0,0,0,0,0"
FONT_HEADER_SANS = "Sans Serif,8,-1,0,50,0,0,0,0,0"

# Positions / Spaltenbreiten (angepasst an das gewünschte Layout)
HEADER_CELLS = [
    ("Seite",       35),
    ("Ziel extern",105),
    ("Kabelnummer",190),
    ("Kabeltyp",   320),
    ("Klemme",     420),
    ("Brücke",     615)
]
HEADER_BG_Y1 = 40
HEADER_BG_Y2 = 65

data_first_y1      = 64.9139
row_height         = 20
max_rows_per_page  = 33

# Line correction offsets (aus deinen Beispielen)
LINE_X_OFFSET = 8    # Korrigiert x der Linie gegenüber Punkt-x
LINE_Y_OFFSET = 18.0   # Korrigiert y der Linie gegenüber Punkt-y
LINE_Z = '13'          # z-Level wie in deinem Beispiel

# ---- definierter Start-Y für Linien, die VON OBEN kommen (unter grauen Header-Boxen)
CONT_LINE_START_Y = HEADER_BG_Y2 + 3

# Brücken-Spalten Indizes (CSV F..O)
BRIDGE_COL_START = 5
BRIDGE_COL_END = 14

# --------------------
# Hilfsfunktionen
# --------------------
def normalize_uuid(uuid_str):
    """Normiert UUIDs aus CSV oder xml: entfernt {} und lowercase."""
    if not uuid_str:
        return ''
    return str(uuid_str).strip().strip('{}').lower()

def generate_klemmenliste(qet_file):
    """Robuste Erzeugung der Klemmenliste via Klemmplan_csv.process_file.
       Bei Fehler wird vollständiger Traceback und eine Vorschau der erzeugten CSV ausgegeben.
    """
    tmp_fd, tmp_path = tempfile.mkstemp(suffix='.csv')
    os.close(tmp_fd)
    rows = []
    try:
        # Aufruf des externen Generators (process_file)
        try:
            process_file(qet_file, tmp_path)
        except SystemExit as se:
            code = getattr(se, 'code', None)
            print(f"FEHLER: Klemmplan_csv.process_file() rief sys.exit({code}).", file=sys.stderr)
            print("Traceback (wo aufgetreten):", file=sys.stderr)
            traceback.print_exc(file=sys.stderr)
            raise RuntimeError(f"process_file called sys.exit({code})")
        except Exception as e:
            # Vollständigen Traceback ausgeben, inkl. Typ und Repr der Exception
            print(f"FEHLER: Klemmplan_csv.process_file() hat eine Exception geworfen: {repr(e)}", file=sys.stderr)
            print("Vollständiger Traceback:", file=sys.stderr)
            traceback.print_exc(file=sys.stderr)

            # Wenn temporäre CSV existiert: zeige Preview (erste 200 Zeilen) zur Diagnose
            try:
                if os.path.exists(tmp_path) and os.path.getsize(tmp_path) > 0:
                    print("DEBUG: Preview der erzeugten CSV (erste 200 Zeilen):", file=sys.stderr)
                    with open(tmp_path, 'r', encoding='utf-8', errors='replace') as f:
                        for i, line in enumerate(f, 1):
                            if i > 200:
                                break
                            print(f"{i:04d}: {line.rstrip()}", file=sys.stderr)
                else:
                    print("DEBUG: keine CSV erzeugt oder Datei leer.", file=sys.stderr)
            except Exception as ex_preview:
                print(f"FEHLER beim Lesen der temporären CSV: {ex_preview}", file=sys.stderr)

            # Re-raise damit Aufrufer das sehen (oder wir brechen kontrolliert)
            raise

        # Prüfe Datei-Größe
        try:
            size = os.path.getsize(tmp_path)
        except Exception as e:
            print(f"FEHLER beim Prüfen der Temporärdatei: {e}", file=sys.stderr)
            size = 0

        if size == 0:
            print(f"Warnung: erzeugte CSV '{tmp_path}' ist leer (size=0).", file=sys.stderr)
        else:
            # Zeige die ersten 200 Zeilen (nur zur Diagnose)
            with open(tmp_path, 'r', encoding='utf-8', errors='replace') as f:
                preview = []
                for i, line in enumerate(f, 1):
                    preview.append(line.rstrip('\n'))
                    if i >= 200:
                        break
            print("DEBUG: Erste Zeilen der erzeugten CSV:", file=sys.stderr)
            for i, line in enumerate(preview, 1):
                print(f"{i:03d}: {line}", file=sys.stderr)

        # Lese CSV sicher ein
        with open(tmp_path, 'r', encoding='utf-8', errors='replace') as f:
            reader = csv.reader(f, delimiter=';')
            try:
                _hdr = next(reader, None)
            except Exception as e:
                print(f"FEHLER beim Lesen des CSV-Headers: {e}", file=sys.stderr)
                _hdr = None
            for row in reader:
                if not any((cell or '').strip() for cell in row):
                    continue
                rows.append(row)

        print(f"DEBUG: {len(rows)} Zeilen eingelesen aus CSV.", file=sys.stderr)
        return rows

    finally:
        try:
            os.unlink(tmp_path)
        except Exception:
            pass

def remove_namespaces(xml_string):
    """
    Robustes Entfernen von XML-Namespaces:
    - Versucht zuerst ElementTree.iterparse zu verwenden und entfernt
      Namespaces aus Tags und Attributnamen sauber (sicherer).
    - Falls iterparse fehlschlägt, fällt ein vorsichtiger Regex-Fallback
      zurück, der Präfixe vor Tags und Attributen entfernt.
    """
    try:
        it = ET.iterparse(io.StringIO(xml_string))
        for _, el in it:
            # Tag: "{ns}tagname" -> "tagname"
            if isinstance(el.tag, str) and '}' in el.tag:
                el.tag = el.tag.split('}', 1)[1]
            # Attribute: "{ns}attr" -> "attr" oder "prefix:attr" -> "attr"
            for attr in list(el.attrib):
                # falls Attr-Name im "{ns}attr" Format
                if '}' in attr:
                    new_attr = attr.split('}', 1)[1]
                    el.attrib[new_attr] = el.attrib.pop(attr)
                # falls Attr-Name mit "prefix:name"
                elif ':' in attr:
                    new_attr = attr.split(':', 1)[1]
                    el.attrib[new_attr] = el.attrib.pop(attr)
        root = it.root
        return ET.tostring(root, encoding='unicode')
    except Exception:
        # Fallback: sichere, gezielte Regex-Bereinigung
        s = re.sub(r'\sxmlns(:\w+)?="[^"]+"', "", xml_string)   # entferne xmlns-Decls
        # entferne Präfix vor Tags (z. B. <svg:tag -> <tag)
        s = re.sub(r'(<\/?)[A-Za-z0-9_-]+:', r'\1', s)
        # entferne Präfix vor Attributnamen (z. B. sodipodi:docname="..." -> docname="...")
        s = re.sub(r'([ \t\r\n])[A-Za-z0-9_-]+:', r'\1', s)
        return s

def find_max_order(root):
    orders = [int(d.get('order','0')) for d in root.findall('.//diagram') if d.get('order','0').isdigit()]
    return max(orders) if orders else 0

def find_property_value(root, name):
    """Sucht value / text von <property name="..."> oder Attribut im root."""
    if root is None:
        return ''
    for prop in root.findall('.//property'):
        if prop.get('name') and prop.get('name').lower() == name.lower():
            val = prop.get('value')
            if val is None:
                val = (prop.text or '').strip()
            return val or ''
    for k, v in root.attrib.items():
        if k.lower() == name.lower():
            return v
    return ''

def sanitize_block_display(s):
    """Entfernt führende '-' und trimmt whitespace für Seitentitel."""
    if not s:
        return ''
    return str(s).lstrip('-').strip()

def clone_template(root, new_order, title):
    tpl = root.find('.//diagram')
    if tpl is None:
        raise RuntimeError("Vorlage <diagram> nicht gefunden.")
    attrs = {k: tpl.get(k,'') for k in [
        'cols','colsize','rows','rowsize','width','height',
        'date','titleblocktemplate','displayAt','auto_page_num',
        'author','folio','locmach','version'
    ]}
    attrs.update({
        'order': str(new_order),
        'title': title,
        'displaycols': 'false',
        'displayrows': 'false',
    })
    new_diag = ET.Element('diagram', attrs)
    props = tpl.find('properties')
    if props is not None:
        new_diag.append(copy.deepcopy(props))
    new_diag.set('folio', 'Listen')
    ET.SubElement(new_diag, 'inputs')
    ET.SubElement(new_diag, 'shapes')
    return new_diag

def extract_element_entries(root, target_name):
    """
    Extrahiert elementInformation-Einträge (älterer Fallback; behalte für Kompatibilität).
    Liefert Liste von dicts: {'val': value, 'blob': searchable_blob}
    """
    entries = []
    if root is None:
        return entries
    parent_map = {c: p for p in root.iter() for c in p}
    for elem in root.findall('.//elementInformation'):
        name_attr = (elem.get('name') or '').strip()
        if name_attr.lower() != target_name.lower():
            continue
        val = (elem.text or '').strip()
        if val == '':
            continue
        parent = parent_map.get(elem)
        blob_parts = []
        if parent is not None:
            blob_parts.append(parent.tag or '')
            for k, v in parent.attrib.items():
                if v:
                    blob_parts.append(str(v))
            for sub in parent.iter():
                if sub is elem:
                    continue
                if sub.text and sub.text.strip():
                    blob_parts.append(sub.text.strip())
                for k, v in sub.attrib.items():
                    if v:
                        blob_parts.append(str(v))
        else:
            blob_parts.append(val)
            for k, v in elem.attrib.items():
                if v:
                    blob_parts.append(str(v))
        search_blob = ' '.join([p for p in blob_parts if p is not None and str(p).strip() != ''])
        entries.append({'val': val, 'blob': search_blob})
    return entries

def build_uuid_info_map(root):
    """
    Baut eine Map uuid_norm -> {'manufacturer_reference':..., 'comment':...}
    Aus den element-Elementen der QET-Datei.
    (Wird hier nicht mehr primär verwendet, bleibt als mögliche Referenz)
    """
    m = {}
    for elem in root.findall('.//element'):
        uuid_raw = elem.get('uuid', '')
        uid = normalize_uuid(uuid_raw)
        if not uid:
            continue
        info = {'manufacturer_reference': '', 'supplier': ''}
        infos = elem.find('elementInformations')
        if infos is not None:
            for ei in infos.findall('elementInformation'):
                name = (ei.get('name') or '').strip().lower()
                val = (ei.text or '').strip()
                if not val:
                    continue
                if name == 'manufacturer_reference':
                    info['manufacturer_reference'] = val
                elif name == 'supplier':
                    info['supplier'] = val
        m[uid] = info
    return m

# Hilfsfunktion: Bridge-X-Positionen (gleich wie in draw_rows)
def compute_bridge_x_positions():
    bridge_n = BRIDGE_COL_END - BRIDGE_COL_START + 1
    bridge_area_x1 = 505.0
    bridge_area_x2 = 775.0
    width = (bridge_area_x2 - bridge_area_x1) / bridge_n
    return [round(bridge_area_x1 + (i + 0.5) * width, 3) for i in range(bridge_n)]

# --------------------
# Zeichnen: Header + Rows
# --------------------
def draw_header(diag, projekt_value, page_index, bmk_block, plant_value=''):
    shapes = diag.find('shapes')
    inputs = diag.find('inputs')

    big_shape = ET.SubElement(shapes, 'shape', {
        'type':'Rectangle','x1':'20','y1':'15','x2':'1090','y2':'40',
        'closed':'0','is_movable':'1','z':'0','rx':'0','ry':'0'
    })
    ET.SubElement(big_shape, 'pen', PEN_SHAPE)
    ET.SubElement(big_shape, 'brush', BRUSH_NO)

    html_title = (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head>'
        '<body style=" font-family:\'Sans Serif\'; font-size:10pt;">'
        '<p style=" margin-top:0px; margin-bottom:0px;"><span style=" font-size:10pt;">Klemmenplan</span></p></body></html>'
    )
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'25', 'y':'15', 'text':html_title})

    html_projekt = (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head>'
        '<body style=" font-family:\'Sans Serif\'; font-size:10pt;">'
        f'<p style=" margin-top:0px; margin-bottom:0px;"><span style=" font-size:10pt;">{escape(projekt_value or "")}</span></p></body></html>'
    )
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'480', 'y':'15', 'text':html_projekt})

    html_seite_label = (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head>'
        '<body style=" font-family:\'Sans Serif\'; font-size:10pt;">'
        '<p style=" margin-top:0px; margin-bottom:0px;"><span style=" font-size:10pt;">Blatt:</span></p></body></html>'
    )
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'1015', 'y':'15', 'text':html_seite_label})

    html_seite_number = (
        '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
        '<html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head>'
        '<body style=" font-family:\'Sans Serif\'; font-size:10pt;">'
        f'<p style=" margin-top:0px; margin-bottom:0px;"><span style=" font-size:10pt;">{escape(str(page_index))}</span></p></body></html>'
    )
    ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'1055', 'y':'15', 'text':html_seite_number})

    if bmk_block:
        html_bmk = (
            '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
            '<html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head>'
            '<body style=" font-family:\'Sans Serif\'; font-size:10pt;">'
            f'<p style=" margin-top:0px; margin-bottom:0px;"><span style=" font-size:10pt;">{escape(bmk_block)}</span></p></body></html>'
        )
        ET.SubElement(inputs, 'input', {'font': FONT_HEADER_SANS, 'rotation':'0', 'x':'115', 'y':'15', 'text':html_bmk})

    statics = [
        ("Klemmentyp", 845, 40),   # früher 820 -> jetzt 845 (zentrierter für erweiterte Breite)
        ("Ebene", 1020, 40),      # früher 1000 -> jetzt 1020
        ("Ziel extern", 105, 40),
        ("Seite", 35, 40),
        ("Kabelnummer", 190, 40),
        ("Kabeltyp", 320, 40),
        ("Klemme", 435, 40),
        ("Brücke", 615, 40),
    ]
    for text, x, y in statics:
        html = (
            '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
            '<html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head>'
            f'<body style=" font-family:\'Noto Sans\'; font-size:9pt;">'
            f'<p style=" margin-top:12px; margin-bottom:12px;">{escape(text)}</p></body></html>'
        )
        ET.SubElement(inputs, 'input', {'font': FONT_HEADER, 'rotation':'0', 'x':str(x), 'y':str(y), 'text':html})

    grey_boxes = [
        (20, 40, 90, 65),
        (90, 40, 185, 65),
        (185, 40, 285, 64.9019),
        (285, 40, 420, 64.9019),
        (420, 40, 505, 64.9019),
        (505, 40, 775, 65),
        (775, 40, 995, 65),   # früher x2=955 -> jetzt x2=995
        (995, 40, 1090, 65),  # früher x1=955 -> jetzt x1=995
    ]
    for x1, y1, x2, y2 in grey_boxes:
        s = ET.SubElement(shapes, 'shape', {
            'type':'Rectangle','x1':str(x1),'y1':str(y1),
            'x2':str(x2),'y2':str(y2),
            'closed':'0','is_movable':'1','z':'0','rx':'0','ry':'0'
        })
        ET.SubElement(s, 'pen', PEN_SHAPE)
        ET.SubElement(s, 'brush', BRUSH_HEADER_GRAY)

def draw_rows(diag, rows, mref_entries, supplier_entries, prev_cols=set(), next_cols=set()):
    shapes = diag.find('shapes')
    inputs = diag.find('inputs')

    BRIDGE_FONT = "Noto Sans,14,-1,5,50,0,0,0,0,0"

    rects = [
        (20, 90), (90, 185), (185, 285), (285, 420),
        (420, 505), (505, 775), (775, 995), (995, 1090)
    ]
    text_positions = [20, 90, 185, 285, 420, 505, 775, 995]

    bridge_x_positions = compute_bridge_x_positions()
    DOT_Y_OFFSET = -7.9139

    bridge_points_by_col = {}

    for idx, row in enumerate(rows):
        row = list(row)
        if len(row) < 6:
            row += [''] * (6 - len(row))

        # --- Keine UUID-Logik mehr: mref & supplier kommen primär aus CSV ---
        page_val = row[0] if len(row) > 0 else ''
        ziel_extern = str(row[1]) if len(row) > 1 else ''
        kabelnummer = str(row[2]) if len(row) > 2 else ''
        kabeltyp = str(row[3]) if len(row) > 3 else ''
        klemme = str(row[4]) if len(row) > 4 else ''

        def match_entry(entries, tokens):
            if not entries:
                return ''
            tkns = [t for t in tokens if t]
            for entry in entries:
                blob = (entry.get('blob','') or '').lower()
                for t in tkns:
                    if re.search(r'(^|[^A-Za-z0-9])' + re.escape(t.lower()) + r'($|[^A-Za-z0-9])', blob):
                        return entry['val']
            if len(entries) == 1:
                return entries[0]['val']
            return ''

        # Versuch: direkt aus CSV die letzten beiden Spalten nehmen (manufacturer_ref, supplier)
        mref_val = ''
        supplier_val = ''
        if len(row) >= 2:
            # sichere Indizierung: letzte zwei Spalten sind Hersteller/Lieferant
            cand_m = row[-2] if len(row) >= 2 else ''
            cand_s = row[-1] if len(row) >= 1 else ''
            cand_m = cand_m.strip() if cand_m is not None else ''
            cand_s = cand_s.strip() if cand_s is not None else ''
            if cand_m:
                mref_val = cand_m
            if cand_s:
                supplier_val = cand_s

        # Fallback: falls CSV-Felder leer, versuche match_entry auf extrahierten elementInformation-Listen
        if not mref_val:
            mref_val = match_entry(mref_entries, [kabelnummer, klemme, ziel_extern])
        if not supplier_val:
            supplier_val = match_entry(supplier_entries, [klemme, kabelnummer, ziel_extern])

        y1 = data_first_y1 + idx * row_height
        y2 = y1 + row_height

        for x1, x2 in rects:
            s = ET.SubElement(shapes, 'shape', {
                'type':'Rectangle','x1':str(x1),'y1':str(y1),
                'x2':str(x2),'y2':str(y2),
                'closed':'0','is_movable':'1','z':'0','rx':'0','ry':'0'
            })
            ET.SubElement(s, 'pen', PEN_SHAPE)
            ET.SubElement(s, 'brush', BRUSH_NO)

        contents = [page_val, ziel_extern, kabelnummer, kabeltyp, klemme, '', mref_val, supplier_val]
        for col, val in enumerate(contents):
            if col >= len(text_positions):
                break
            if val is None or str(val).strip() == '':
                continue
            x = text_positions[col]
            ty = round(y1)
            html = (
                '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
                '<html><head><meta name="qrichtext" content="1" /><style type="text/css">p, li { white-space: pre-wrap; }</style></head>'
                '<body style=" font-family:\'Noto Sans\'; font-size:8pt;">'
                f'<p align="center" style=" margin-top:0px; margin-bottom:0px;"><span style=" font-size:6pt;">{escape(str(val))}</span></p></body></html>'
            )
            ET.SubElement(inputs, 'input', {
                'font': FONT_ROW, 'rotation':'0', 'x':str(x), 'y':str(ty), 'text':html
            })

        # Brückenpunkte: 1:1 aus CSV-Spalten F..O übernehmen
        bridge_targets_x = []
        for col_idx in range(BRIDGE_COL_START, BRIDGE_COL_END + 1):
            if col_idx < len(row):
                cell = row[col_idx]
                if cell is not None and str(cell).strip() != '':
                    pos_index = col_idx - BRIDGE_COL_START
                    if 0 <= pos_index < len(bridge_x_positions):
                        bridge_targets_x.append(bridge_x_positions[pos_index])

        if bridge_targets_x:
            seen = set()
            bridge_targets_x = [x for x in bridge_targets_x if not (x in seen or seen.add(x))]

        for bx in bridge_targets_x:
            ty_dot = round(y1 + DOT_Y_OFFSET, 3)
            html_dot = (
                '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'
                '<html><body><p style="margin:0;"><span style="font-size:14pt;">•</span></p></body></html>'
            )
            ET.SubElement(inputs, 'input', {
                'font': BRIDGE_FONT, 'rotation':'0',
                'x': str(bx), 'y': str(ty_dot), 'text': html_dot
            })

            bridge_points_by_col.setdefault(bx, []).append((float(bx), float(ty_dot)))

    # After drawing points: draw lines per column
    # Determine bottom y for this page (table end)
    if rows:
        last_row_idx = len(rows) - 1
        y_last_bottom = data_first_y1 + last_row_idx * row_height + row_height
        y_bottom_line = round(y_last_bottom + LINE_Y_OFFSET, 3)
    else:
        y_bottom_line = round(data_first_y1 + LINE_Y_OFFSET, 3)

    for bx, pts in bridge_points_by_col.items():
        if not pts:
            continue

        current_has_prev = bx in prev_cols
        current_has_next = bx in next_cols

        # If only one point on the page:
        if len(pts) == 1:
            x_pt, y_pt = pts[0]
            x_line = round(x_pt + LINE_X_OFFSET, 3)
            y_line_pt = round(y_pt + LINE_Y_OFFSET, 3)

            # If this column continues from previous page -> draw from header-start to point
            if current_has_prev:
                y_line_start = round(CONT_LINE_START_Y, 3)
                ln = ET.SubElement(shapes, 'shape', {
                    'type': 'Line',
                    'is_movable': '1',
                    'x1': str(x_line), 'y1': str(y_line_start),
                    'x2': str(x_line), 'y2': str(y_line_pt),
                    'closed': '0', 'z': LINE_Z
                })
                ET.SubElement(ln, 'pen', PEN_SHAPE)
                ET.SubElement(ln, 'brush', BRUSH_NO)

            # If this column continues to next page -> draw from point to bottom
            if current_has_next:
                ln2 = ET.SubElement(shapes, 'shape', {
                    'type': 'Line',
                    'is_movable': '1',
                    'x1': str(x_line), 'y1': str(y_line_pt),
                    'x2': str(x_line), 'y2': str(y_bottom_line),
                    'closed': '0', 'z': LINE_Z
                })
                ET.SubElement(ln2, 'pen', PEN_SHAPE)
                ET.SubElement(ln2, 'brush', BRUSH_NO)

            # If neither prev nor next -> nothing else (single isolated point)
            continue

        # If >= 2 points: create segments. For the very first segment, if column continues from prev page,
        # start at CONT_LINE_START_Y; otherwise start at first point y.
        for i in range(len(pts) - 1):
            x1_pt, y1_pt = pts[i]
            x2_pt, y2_pt = pts[i+1]

            x1_line = round(x1_pt + LINE_X_OFFSET, 3)
            x2_line = round(x2_pt + LINE_X_OFFSET, 3)
            y2_line = round(y2_pt + LINE_Y_OFFSET, 3)

            if i == 0:
                if current_has_prev:
                    y1_line = round(CONT_LINE_START_Y, 3)
                else:
                    y1_line = round(y1_pt + LINE_Y_OFFSET, 3)
            else:
                y1_line = round(y1_pt + LINE_Y_OFFSET, 3)

            if abs(x1_line - x2_line) < 1e-6 and abs(y1_line - y2_line) < 1e-6:
                continue

            ln = ET.SubElement(shapes, 'shape', {
                'type': 'Line',
                'is_movable': '1',
                'x1': str(x1_line), 'y1': str(y1_line),
                'x2': str(x2_line), 'y2': str(y2_line),
                'closed': '0', 'z': LINE_Z
            })
            ET.SubElement(ln, 'pen', PEN_SHAPE)
            ET.SubElement(ln, 'brush', BRUSH_NO)

        # After drawing internal segments, if this column continues to the next page, draw from last point to bottom
        if current_has_next:
            last_x_pt, last_y_pt = pts[-1]
            x_last_line = round(last_x_pt + LINE_X_OFFSET, 3)
            y_last_line = round(last_y_pt + LINE_Y_OFFSET, 3)
            # Avoid zero-length
            if not (abs(x_last_line - x_last_line) < 1e-6 and abs(y_last_line - y_bottom_line) < 1e-6):
                ln_end = ET.SubElement(shapes, 'shape', {
                    'type': 'Line',
                    'is_movable': '1',
                    'x1': str(x_last_line), 'y1': str(y_last_line),
                    'x2': str(x_last_line), 'y2': str(y_bottom_line),
                    'closed': '0', 'z': LINE_Z
                })
                ET.SubElement(ln_end, 'pen', PEN_SHAPE)
                ET.SubElement(ln_end, 'brush', BRUSH_NO)


# --------------------
# Main: Pagination & Erzeugung (jetzt block->pages->draw)
# --------------------
def normalize_prefix_for_compare(s):
    if not s:
        return ''
    t = str(s).strip()
    t = t.replace('\u00A0', ' ').strip()
    t = re.sub(r'\s+', '', t)
    t = re.sub(r'^[^A-Za-z0-9]+', '', t)
    return t.lower()

def compute_page_bridge_cols(rows):
    """Gibt Menge von bridge-x-positions zurück, die auf dieser Seite gesetzt sind."""
    bx_positions = compute_bridge_x_positions()
    cols = set()
    for row in rows:
        for col_idx in range(BRIDGE_COL_START, BRIDGE_COL_END + 1):
            if col_idx < len(row) and row[col_idx] is not None and str(row[col_idx]).strip() != '':
                pos_index = col_idx - BRIDGE_COL_START
                if 0 <= pos_index < len(bx_positions):
                    cols.add(bx_positions[pos_index])
    return cols

def main():
    if len(sys.argv) != 3:
        print("Usage: python3 Klemmenplan_qet.py <in.qet> <out.qet>")
        sys.exit(1)
    inp, out = sys.argv[1], sys.argv[2]
    xml = open(inp, 'r', encoding='utf-8').read()
    root = ET.fromstring(remove_namespaces(xml))

    first_diag = root.find('.//diagram')
    # --- plant_value wie vorher (unverändert) ---
    plant_value = ''
    if first_diag is not None:
        plant_value = first_diag.get('plant', '') or first_diag.get('Plant', '')
    if not plant_value:
        plant_value = find_property_value(root, 'plant') or find_property_value(root, 'Plant')

    # --- projekt_value jetzt primär aus <project title="..."> entnehmen ---
    projekt_value = ''
    try:
        # Fall 1: root selbst ist <project> und hat title
        if isinstance(root, ET.Element) and root.tag.lower() == 'project' and root.get('title'):
            projekt_value = root.get('title').strip()
        else:
            # Fall 2: suche explizit nach einem <project>-Element irgendwo im Dokument
            proj_elem = root.find('.//project')
            if proj_elem is not None and proj_elem.get('title'):
                projekt_value = proj_elem.get('title').strip()
    except Exception:
        projekt_value = ''

    # Fallbacks (falls title nicht vorhanden)
    if not projekt_value:
        projekt_value = find_property_value(root, 'projekt') or find_property_value(root, 'Projekt')

    if not projekt_value:
        try:
            projekt_value = os.path.splitext(os.path.basename(inp))[0]
        except Exception:
            projekt_value = ''


    mref_entries = extract_element_entries(root, 'manufacturer_reference')
    supplier_entries = extract_element_entries(root, 'supplier')
    # uuid_info_map = build_uuid_info_map(root)  # nicht mehr primär verwendet

    data = generate_klemmenliste(inp)
    base = find_max_order(root)

    # 1) Zuerst data in Blöcke (pro BMK-Prefix) einteilen
    blocks = []  # list of (block_display, rows_list)
    current_block_rows = []
    current_block_display = ''
    current_block_norm = None

    for r in data:
        klemme_field = ''
        if r and len(r) > 4 and isinstance(r[4], str):
            klemme_field = r[4].strip()

        prefix = ''
        if klemme_field:
            if ':' in klemme_field:
                prefix = klemme_field.split(':', 1)[0]
            elif 'W' in klemme_field:
                prefix = klemme_field.split('W', 1)[0]
            else:
                prefix = klemme_field

        norm_pref = normalize_prefix_for_compare(prefix)
        if norm_pref == '' and current_block_norm:
            norm_pref = current_block_norm

        if current_block_norm is None:
            current_block_norm = norm_pref
            current_block_display = prefix or ''

        # Blockwechsel?
        if norm_pref != current_block_norm:
            if current_block_rows:
                blocks.append((current_block_display, current_block_rows))
            current_block_rows = []
            current_block_display = prefix or ''
            current_block_norm = norm_pref

        current_block_rows.append(r)

    if current_block_rows:
        blocks.append((current_block_display, current_block_rows))

    # 2) Für jeden Block: in Seiten aufteilen, dann Seiten erzeugen und draw_rows mit prev/next-Infos aufrufen
    order_counter = base
    for block_display, block_rows in blocks:
        # split block_rows into pages of max_rows_per_page
        pages = [block_rows[i:i+max_rows_per_page] for i in range(0, len(block_rows), max_rows_per_page)]
        # compute per-page bridge columns
        pages_cols = [compute_page_bridge_cols(p) for p in pages]

        # per-block page counter (start at 0, will be incremented to 1 for first page)
        page_counter_block = 0

        for i, page_rows in enumerate(pages):
            order_counter += 1
            page_counter_block += 1
            cleaned = sanitize_block_display(block_display)
            title = f"Klemmenplan -{cleaned}" if cleaned else "Klemmenplan"
            diag = clone_template(root, order_counter, title)
            if plant_value:
                diag.set('plant', plant_value)

            prev_cols = pages_cols[i-1] if i-1 >= 0 else set()
            next_cols = pages_cols[i+1] if i+1 < len(pages_cols) else set()

            draw_header(diag, projekt_value, page_counter_block, block_display, plant_value)
            draw_rows(diag, page_rows, mref_entries, supplier_entries, prev_cols, next_cols)
            root.append(diag)

    ET.ElementTree(root).write(out, encoding='utf-8', xml_declaration=True)
    print(f"Klemmenplan erstellt: {out}")

if __name__ == '__main__':
    main()
