#!/usr/bin/env python3
# SPS-Editor.py (überarbeitet, Version zurückgesetzt + korrekte deutsche Überschriften)
# Änderungen:
# - Version basiert auf der vorherigen (vor der automatischen deutsch-Übersetzungsänderung)
# - Spaltenköpfe wurden jetzt genau nach deiner Zuordnung übersetzt
# - Beim Öffnen werden alle Seiten geladen; linke Liste zeigt "folio - title"
# - Backup und atomares Schreiben bleiben erhalten
#
# Benötigt: PySide6, defusedxml
# pip install PySide6 defusedxml

import sys
import os
import time
import traceback
import shutil
import tempfile
from PySide6.QtWidgets import (
    QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QListWidget,
    QFileDialog, QMessageBox, QTableWidget, QTableWidgetItem, QLabel, QHeaderView,
    QSplitter
)
from PySide6.QtGui import QKeySequence, QShortcut
from PySide6.QtCore import Qt
from defusedxml import ElementTree as ET

LOGFILE = os.path.expanduser("~/.local/share/sps-editor-start.log")

def log(msg):
    try:
        with open(LOGFILE, "a", encoding="utf-8") as f:
            f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {msg}\n")
    except Exception:
        pass

# Interne Feldliste (Schlüssel) wie vom Nutzer vorgegeben
COLUMNS = [
    'label','plant','location','comment','function','desciption','designation',
    'manufacturer','manufacturer_reference','machine_manufacturer_reference','supplier',
    'quantity','unity',
    'auxiliary1','description_auxiliary1','designation_auxiliary1','manufacturer_auxiliary1',
    'manufacturer_reference_auxiliary1','machine_manufacturer_reference_auxiliary1','supplier_auxiliary1',
    'quantity_auxiliary1','unity_auxiliary1',
    'auxiliary2','description_auxiliary2','designation_auxiliary2','manufacturer_auxiliary2',
    'manufacturer_reference_auxiliary2','machine_manufacturer_reference_auxiliary2','supplier_auxiliary2',
    'quantity_auxiliary2','unity_auxiliary2',
    'auxiliary3','description_auxiliary3','designation_auxiliary3','manufacturer_auxiliary3',
    'manufacturer_reference_auxiliary3','machine_manufacturer_reference_auxiliary3','supplier_auxiliary3',
    'quantity_auxiliary3','unity_auxiliary3',
    'auxiliary4','description_auxiliary4','designation_auxiliary4','manufacturer_auxiliary4',
    'manufacturer_reference_auxiliary4','machine_manufacturer_reference_auxiliary4','supplier_auxiliary4',
    'quantity_auxiliary4','unity_auxiliary4'
]

# Deine korrekten Übersetzungen (Header -> Schlüssel in Klammern)
GERMAN_HEADERS = {
    'label': 'BMK',
    'plant': 'Anlage',
    'location': 'Ort',
    'comment': 'Kommentar',
    'function': 'Funktion',
    'desciption': 'Artikelbeschreibung',
    'designation': 'Artikelnummer',
    'manufacturer': 'Hersteller',
    'manufacturer_reference': 'Bestellnummer',
    'machine_manufacturer_reference': 'Interne Nummer',
    'supplier': 'Lieferant',
    'quantity': 'Menge',
    'unity': 'Einheit',
    'auxiliary1': 'Zusatzinfo Zusatzartikel 1',
    'description_auxiliary1': 'Artikelbeschreibung Zusatzartikel 1',
    'designation_auxiliary1': 'Artikelnummer Zusatzartikel 1',
    'manufacturer_auxiliary1': 'Hersteller Zusatzartikel 1',
    'manufacturer_reference_auxiliary1': 'Bestellnummer Zusatzartikel 1',
    'machine_manufacturer_reference_auxiliary1': 'Interne Nummer Zusatzartikel 1',
    'supplier_auxiliary1': 'Lieferant Zusatzartikel 1',
    'quantity_auxiliary1': 'Menge Zusatzartikel 1',
    'unity_auxiliary1': 'Einheit Zusatzartikel 1',
    'auxiliary2': 'Zusatzinfo Zusatzartikel 2',
    'description_auxiliary2': 'Artikelbeschreibung Zusatzartikel 2',
    'designation_auxiliary2': 'Artikelnummer Zusatzartikel 2',
    'manufacturer_auxiliary2': 'Hersteller Zusatzartikel 2',
    'manufacturer_reference_auxiliary2': 'Bestellnummer Zusatzartikel 2',
    'machine_manufacturer_reference_auxiliary2': 'Interne Nummer Zusatzartikel 2',
    'supplier_auxiliary2': 'Lieferant Zusatzartikel 2',
    'quantity_auxiliary2': 'Menge Zusatzartikel 2',
    'unity_auxiliary2': 'Einheit Zusatzartikel 2',
    'auxiliary3': 'Zusatzinfo Zusatzartikel 3',
    'description_auxiliary3': 'Artikelbeschreibung Zusatzartikel 3',
    'designation_auxiliary3': 'Artikelnummer Zusatzartikel 3',
    'manufacturer_auxiliary3': 'Hersteller Zusatzartikel 3',
    'manufacturer_reference_auxiliary3': 'Bestellnummer Zusatzartikel 3',
    'machine_manufacturer_reference_auxiliary3': 'Interne Nummer Zusatzartikel 3',
    'supplier_auxiliary3': 'Lieferant Zusatzartikel 3',
    'quantity_auxiliary3': 'Menge Zusatzartikel 3',
    'unity_auxiliary3': 'Einheit Zusatzartikel 3',
    'auxiliary4': 'Zusatzinfo Zusatzartikel 4',
    'description_auxiliary4': 'Artikelbeschreibung Zusatzartikel 4',
    'designation_auxiliary4': 'Artikelnummer Zusatzartikel 4',
    'manufacturer_auxiliary4': 'Hersteller Zusatzartikel 4',
    'manufacturer_reference_auxiliary4': 'Bestellnummer Zusatzartikel 4',
    'machine_manufacturer_reference_auxiliary4': 'Interne Nummer Zusatzartikel 4',
    'supplier_auxiliary4': 'Lieferant Zusatzartikel 4',
    'quantity_auxiliary4': 'Menge Zusatzartikel 4',
    'unity_auxiliary4': 'Einheit Zusatzartikel 4'
}

COLUMN_DEFS = [(name, GERMAN_HEADERS.get(name, name.replace('_', ' '))) for name in COLUMNS]

# --- Backup + safe write helpers ---
def create_single_backup(path):
    try:
        bak = path + ".bak"
        shutil.copy2(path, bak)
        return bak
    except Exception:
        raise

def safe_write_tree(tree, dest_path):
    dirn = os.path.dirname(dest_path) or "."
    fd, tmpname = tempfile.mkstemp(prefix=".tmp_sps_", suffix=".qet", dir=dirn)
    os.close(fd)
    try:
        tree.write(tmpname, encoding='utf-8', xml_declaration=True)
        os.replace(tmpname, dest_path)
    except Exception:
        try:
            os.remove(tmpname)
        except Exception:
            pass
        raise

# ---------- XML / folio helpers ----------
def collect_all_folios(root):
    folios = []
    for el in root.iter():
        f = el.get('folio')
        if f is not None:
            folios.append((f, el))
    return folios

def get_page_title(page_elem):
    title = page_elem.get('title') or page_elem.get('name') or page_elem.get('folio')
    if title is None:
        title = "<unnamed>"
    return title

def collect_elements_on_page(page_elem):
    out = []
    for el in page_elem.findall('.//element'):
        x = el.get('x'); y = el.get('y')
        try:
            xf = float(x) if x is not None else 0.0
            yf = float(y) if y is not None else 0.0
        except ValueError:
            xf, yf = 0.0, 0.0
        info = {}
        ei_parent = el.find('elementInformations')
        if ei_parent is not None:
            for info_node in ei_parent.findall('elementInformation'):
                name = info_node.get('name')
                val = (info_node.text or "").strip()
                if name:
                    info[name] = val
        has_any = any((info.get(fname, "").strip() != "") for fname, _ in COLUMN_DEFS)
        if not has_any:
            continue
        out.append({'elem': el, 'x': xf, 'y': yf, 'fields': info})
    return out

def column_major_sort(elements, x_tolerance=10.0):
    for e in elements:
        e['colx'] = round(e['x'] / x_tolerance) * x_tolerance
    elems_sorted = sorted(elements, key=lambda e: (e['colx'], e['y']))
    return elems_sorted

# ---------- GUI ----------
class SPS_Editor(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("SPS-Editor")
        self.resize(1550, 920)

        self.qet_path = None
        self.tree = None
        self.root = None
        self.pages = []  # list of tuples: (page_elem, display, folio)
        self.page_elements = {}
        self.current_folio = None
        self.current_page_elem = None

        layout = QVBoxLayout(self)
        top_row = QHBoxLayout()
        self.file_label = QLabel("Keine Datei geladen")
        top_row.addWidget(self.file_label)
        open_btn = QPushButton("Datei öffnen")
        open_btn.clicked.connect(self.open_file)
        top_row.addWidget(open_btn)
        layout.addLayout(top_row)

        splitter = QSplitter(Qt.Horizontal)
        left_widget = QWidget(); left_layout = QVBoxLayout(left_widget)
        left_layout.addWidget(QLabel("Seiten (Doppelklick zum Öffnen)"))
        self.pages_list = QListWidget()
        self.pages_list.itemDoubleClicked.connect(self.on_page_double_clicked)
        left_layout.addWidget(self.pages_list)
        splitter.addWidget(left_widget)

        right_widget = QWidget(); right_layout = QVBoxLayout(right_widget)
        self.table_label = QLabel("Seite: -"); right_layout.addWidget(self.table_label)
        self.table = QTableWidget()
        self.table.setColumnCount(len(COLUMN_DEFS))
        headers = [h for (_, h) in COLUMN_DEFS]
        self.table.setHorizontalHeaderLabels(headers)
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
        for i in range(len(COLUMN_DEFS)):
            self.table.setColumnWidth(i, 140)
        self.table.horizontalHeader().setSectionsMovable(False)
        self.table.horizontalHeader().setStretchLastSection(False)
        right_layout.addWidget(self.table, 1)

        btn_row = QHBoxLayout()
        save_btn = QPushButton("Übernehmen (in .qet schreiben)")
        save_btn.clicked.connect(self.apply_changes_to_qet)
        btn_row.addWidget(save_btn)
        right_layout.addLayout(btn_row)

        splitter.addWidget(right_widget)
        layout.addWidget(splitter)

        splitter.setSizes([340, 980])
        self.pages_list.setMaximumWidth(540)

        try:
            QShortcut(QKeySequence("Ctrl+V"), self, activated=self.paste_from_clipboard)
        except Exception:
            pass

    def open_file(self):
        path, _ = QFileDialog.getOpenFileName(self, "QET Datei öffnen", "", "QElectroTech (*.qet);;Alle Dateien (*)")
        if not path:
            return
        try:
            tree = ET.parse(path)
            root = tree.getroot()
        except Exception as e:
            QMessageBox.critical(self, "Fehler", f"Datei konnte nicht geladen werden:\n{e}")
            return

        try:
            bak_path = create_single_backup(path)
            log(f"Backup erstellt: {bak_path}")
        except Exception as e:
            QMessageBox.information(self, "Backup-Fehler", f"Backup konnte nicht erstellt werden:\n{e}\nVorgang wird trotzdem fortgesetzt.")

        self.qet_path = path; self.tree = tree; self.root = root
        self.file_label.setText(os.path.basename(path) + " (Backup erstellt)")

        # load ALL pages immediately and populate left list
        self.pages_list.clear(); self.page_elements.clear(); self.pages = []
        folio_items = collect_all_folios(self.root)
        seen_ids = set()
        for f, el in folio_items:
            if id(el) in seen_ids:
                continue
            seen_ids.add(id(el))
            folio = f.strip()
            title_attr = el.get('title') or ''
            display = f"{folio} - {title_attr}"
            elems = collect_elements_on_page(el)
            elems_sorted = column_major_sort(elems, x_tolerance=10.0)
            self.pages.append((el, display, folio))
            self.pages_list.addItem(display)
            self.page_elements[folio] = elems_sorted

        QMessageBox.information(self, "Geladen", f"{len(self.pages)} Seite(n) in Übersicht geladen.")

    def on_page_double_clicked(self, item):
        text = item.text(); folio = None; page_elem = None
        for p, disp, f in self.pages:
            if disp == text:
                page_elem = p; folio = f; break
        if folio is None: return
        self.current_folio = folio; self.current_page_elem = page_elem
        self.populate_table_for_folio(folio)

    def populate_table_for_folio(self, folio):
        elems = self.page_elements.get(folio, [])
        self.table.setRowCount(len(elems))
        self.table_label.setText(f"Seite: folio={folio}  |  Elemente: {len(elems)}")
        for r, e in enumerate(elems):
            fields = e.get('fields', {})
            for c, (field_name, header) in enumerate(COLUMN_DEFS):
                val = fields.get(field_name, "")
                item = QTableWidgetItem(val)
                item.setFlags(item.flags() | Qt.ItemIsEditable)
                self.table.setItem(r, c, item)

    def paste_from_clipboard(self):
        cb = QApplication.clipboard().text()
        if not cb:
            QMessageBox.information(self, "Clipboard leer", "Kein Text in der Zwischenablage.")
            return
        rows = [row.split('\t') for row in cb.splitlines()]
        rows = [[cell.strip() for cell in row] for row in rows]
        rows = [r for r in rows if any(cell != "" for cell in r)]
        if not rows:
            QMessageBox.information(self, "Keine Daten", "Zwischenablage enthält keine tabellarischen Daten (oder nur leere Zeilen).")
            return

        sel_ranges = self.table.selectedRanges()
        if sel_ranges:
            sel = sel_ranges[0]
            start_row = sel.topRow()
            start_col = sel.leftColumn()
        else:
            cur_row = self.table.currentRow()
            cur_col = self.table.currentColumn()
            start_row = cur_row if cur_row >= 0 else 0
            start_col = cur_col if cur_col >= 0 else 0

        single_col_clip = all(len(r) == 1 for r in rows)
        if single_col_clip:
            header_lower = [hdr.lower() for hdr in [h for (_, h) in COLUMN_DEFS]]
            try:
                start_col = header_lower.index('funktion')
            except ValueError:
                start_col = 0

        for i, rdata in enumerate(rows):
            target_row = start_row + i
            if target_row >= self.table.rowCount():
                self.table.insertRow(target_row)
            for j, val in enumerate(rdata):
                target_col = start_col + j
                if target_col >= self.table.columnCount():
                    break
                self.table.setItem(target_row, target_col, QTableWidgetItem(val))

        QMessageBox.information(self, "Eingefügt", f"{len(rows)} Zeile(n) aus der Zwischenablage eingefügt (Start: row {start_row}, col {start_col}).")

    def apply_changes_to_qet(self):
        if not self.qet_path or not self.tree:
            QMessageBox.warning(self, "Keine Datei", "Bitte zuerst eine .qet Datei öffnen.")
            return
        if not self.current_folio:
            QMessageBox.warning(self, "Keine Seite", "Bitte eine Seite auswählen (Doppelklick).")
            return
        elems = self.page_elements.get(self.current_folio, [])
        changed = 0
        for r in range(self.table.rowCount()):
            if r >= len(elems): break
            e = elems[r]; elem_node = e['elem']
            ei_parent = elem_node.find('elementInformations')
            if ei_parent is None:
                ei_parent = ET.SubElement(elem_node, 'elementInformations')
            name_map = {n.get('name'): n for n in ei_parent.findall('elementInformation') if n.get('name')}
            for c, (field_name, header) in enumerate(COLUMN_DEFS):
                item = self.table.item(r, c)
                newval = (item.text() if item else "").strip()
                oldnode = name_map.get(field_name)
                oldval = (oldnode.text or "").strip() if oldnode is not None else ""
                if newval != oldval:
                    changed += 1
                    if oldnode is not None:
                        oldnode.text = newval
                    else:
                        newnode = ET.SubElement(ei_parent, 'elementInformation')
                        newnode.set('name', field_name)
                        newnode.set('show', '1')
                        newnode.text = newval
                    e['fields'][field_name] = newval

        if changed == 0:
            QMessageBox.information(self, "Keine Änderungen", "Es wurden keine Änderungen festgestellt.")
            return

        try:
            safe_write_tree(self.tree, self.qet_path)
        except Exception as e:
            QMessageBox.critical(self, "Schreibfehler", f"Fehler beim Schreiben der Datei:\n{e}\nBackup bleibt: {self.qet_path}.bak")
            return

        QMessageBox.information(self, "Fertig", f"{changed} Feld(ern) geschrieben. Backup: {os.path.basename(self.qet_path)}.bak")

# ---------- main with logging and exception handling ----------
def main():
    log("Start SPS-Editor (überarbeitet)")
    try:
        app = QApplication(sys.argv)
        w = SPS_Editor()
        w.show()
        rc = app.exec()
        log(f"Exit with code {rc}")
        sys.exit(rc)
    except Exception as e:
        tb = traceback.format_exc()
        log("Uncaught exception:\n" + tb)
        try:
            app = QApplication.instance() or QApplication(sys.argv)
            QMessageBox.critical(None, "Absturz", f"Unbehandelter Fehler beim Start:\n{e}\n\nDetails wurden in {LOGFILE} geloggt.")
        except Exception:
            pass
        print(tb, file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()
