/*
 *	Copyright 2006-2025 The QElectroTech Team
 *	This file is part of QElectroTech.
 *	...
 *	(license header unchanged)
 */

#include "projectprintwindow.h"

#include "../diagram.h"
#include "../qeticons.h"
#include "../qetproject.h"
#include "../qetversion.h"

#include "../qetgraphicsitem/element.h"
#include "../qetgraphicsitem/reportelement.h"
#include "../qetgraphicsitem/dynamicelementtextitem.h"
#include "../qetgraphicsitem/crossrefitem.h"

#include "ui_projectprintwindow.h"

#include <QMarginsF>
#include <QPageSetupDialog>
#include <QPainter>
#include <QPrintDialog>
#include <QPrintPreviewWidget>
#include <QScreen>
#include <QFileDialog>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QSettings>
#include <limits>

// --- helper to get a reasonable class name for QGraphicsItem (projectprintwindow.cpp) ---
#include <typeinfo>
#ifdef __GNUG__
#include <cxxabi.h>
#endif

static QString classNameOf(const QGraphicsItem *item)
{
	if (!item) return QString("nullptr");

	// common project-specific types (fast, readable)
	if (qgraphicsitem_cast<const Element*>(item)) return QStringLiteral("Element");
	if (qgraphicsitem_cast<const ReportElement*>(item)) return QStringLiteral("ReportElement");
	if (qgraphicsitem_cast<const DynamicElementTextItem*>(item)) return QStringLiteral("DynamicElementTextItem");
	if (qgraphicsitem_cast<const CrossRefItem*>(item)) return QStringLiteral("CrossRefItem");
	if (qgraphicsitem_cast<const QGraphicsTextItem*>(item)) return QStringLiteral("QGraphicsTextItem");
	if (qgraphicsitem_cast<const QGraphicsSimpleTextItem*>(item)) return QStringLiteral("QGraphicsSimpleTextItem");

	// fallback: use RTTI; demangle on GNU if possible
	const std::type_info &ti = typeid(*item);
	#ifdef __GNUG__
	int status = 0;
	char *dem = abi::__cxa_demangle(ti.name(), nullptr, nullptr, &status);
	QString ret;
	if (status == 0 && dem) {
		ret = QString::fromUtf8(dem);
		free(dem);
	} else {
		ret = QString::fromLatin1(ti.name());
	}
	return ret;
	#else
	return QString::fromLatin1(ti.name());
	#endif
}

/**
 * Minimal, saubere Implementierung:
 * - requestPaint() erstellt einen Painter und ruft printDiagram() für jedes Diagramm auf.
 * - printDiagram() ist verantwortlich für Seitenzählung und zeichnet direkt nach Rendern die Debug-Rechtecke.
 * - addPdfLinks/addPdfTargets mappen Diagramm-Koordinaten -> Geräte-Koordinaten (fit/tiled).
 */

/* --------------------------------------------------------------------------- */
/* launchDialog / docName / Konstruktor / Destruktor (unverändert bzw. minimal) */
/* --------------------------------------------------------------------------- */

void ProjectPrintWindow::launchDialog(QETProject *project, QPrinter::OutputFormat format, QWidget *parent)
{
	qInfo("void ProjectPrintWindow::launchDialog(...)");
	auto printer_ = new QPrinter();
	QPrinter printer(QPrinter::HighResolution);
	printer_->setDocName(ProjectPrintWindow::docName(project));
	printer_->setPageOrientation(QPageLayout::Landscape);

	if (format == QPrinter::NativeFormat) //To physical printer
	{
		QPrintDialog print_dialog(printer_, parent);
		#ifdef Q_OS_MACOS
		print_dialog.setWindowFlags(Qt::Sheet);
		#endif
		print_dialog.setWindowTitle(tr("Options d'impression", "window title"));
		print_dialog.setOptions(QAbstractPrintDialog::PrintShowPageSize);
		if (print_dialog.exec() == QDialog::Rejected) {
			delete  printer_;
			return;
		}
	}
	else //To pdf file
	{
		auto dir_path = project->currentDir();
		QString file_name = QDir::toNativeSeparators(QDir::cleanPath(dir_path % "/" % printer_->docName()));
		if (!file_name.endsWith(".pdf")) {
			file_name.append(".pdf");
		}
		printer_->setCreator(QString("QElectroTech %1").arg(QetVersion::displayedVersion()));
		printer_->setOutputFileName(file_name);
		printer_->setOutputFormat(QPrinter::PdfFormat);
	}

	auto w = new ProjectPrintWindow(project, printer_, parent);
	w->showMaximized();
}

QString ProjectPrintWindow::docName(QETProject *project)
{
	qInfo("QString ProjectPrintWindow::docName(...)");
	QString doc_name;
	if (!project->filePath().isEmpty()) {
		doc_name = QFileInfo(project->filePath()).baseName();
	} else if (!project->title().isEmpty()) {
		doc_name = project->title();
		doc_name = QET::stringToFileName(doc_name);
	}

	if (doc_name.isEmpty()) {
		doc_name = tr("projet", "string used to generate a filename");
	}

	return doc_name;
}

ProjectPrintWindow::ProjectPrintWindow(QETProject *project, QPrinter *printer, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ProjectPrintWindow),
m_project(project),
m_printer(printer)
{
	qInfo("ProjectPrintWindow::ProjectPrintWindow(...)");
	ui->setupUi(this);

	loadPageSetupForCurrentPrinter();

	m_preview = new QPrintPreviewWidget(m_printer);
	connect(m_preview, &QPrintPreviewWidget::paintRequested, this, &ProjectPrintWindow::requestPaint);
	ui->m_vertical_layout->addWidget(m_preview);

	setUpDiagramList();

	if (m_printer->outputFormat() == QPrinter::NativeFormat) //Print to physical printer
	{
		auto print_button = new QPushButton(QET::Icons::DocumentPrint, tr("Imprimer"));
		ui->m_button_box->addButton(print_button, QDialogButtonBox::ActionRole);
		connect(print_button, &QPushButton::clicked, this, &ProjectPrintWindow::print);
	}
	else	//export to pdf
	{
		auto pdf_button = new QPushButton(QET::Icons::PDF, tr("Exporter en pdf"));
		ui->m_button_box->addButton(pdf_button, QDialogButtonBox::ActionRole);
		connect(pdf_button, &QPushButton::clicked, this, &ProjectPrintWindow::exportToPDF);
	}

	auto exp = ExportProperties::defaultPrintProperties();
	ui->m_draw_border_cb->setChecked(exp.draw_border);
	ui->m_draw_titleblock_cb->setChecked(exp.draw_titleblock);
	ui->m_draw_terminal_cb->setChecked(exp.draw_terminals);
	ui->m_keep_conductor_color_cb->setChecked(exp.draw_colored_conductors);

	ui->m_date_cb->blockSignals(true);
	ui->m_date_cb->setDate(QDate::currentDate());
	ui->m_date_cb->blockSignals(false);

	#ifdef Q_OS_WINDOWS
	if (m_printer->outputFormat() == QPrinter::PdfFormat)
	{
		ui->m_page_setup->setDisabled(true);
		ui->m_page_setup->setText(tr("Mise en page (non disponible sous Windows pour l'export PDF)"));
	}
	#endif

	m_backup_diagram_background_color = Diagram::background_color;
	Diagram::background_color = Qt::white;
}

ProjectPrintWindow::~ProjectPrintWindow()
{
	delete ui;
	delete m_printer;
	Diagram::background_color = m_backup_diagram_background_color;
}

/* --------------------------------------------------------------------------- */
/* readXrefList: speichert Positionen relativ zur Diagram-TopLeft (robust)     */
/* --------------------------------------------------------------------------- */

void ProjectPrintWindow::readXrefList(QList<xref>& xrefList)
{
	qInfo();
	qInfo("***** find positions for PDF-internal links...");
	uint pageToPrint = 0;
	uint linkNumber = 0;
	QMap<int,int> pagesMap; // FolioIndex as key, PDF-page as value
	xrefList.clear();

	for (auto diagram : selectedDiagram())
	{
		pageToPrint++;
		pagesMap[diagram->folioIndex()] = pageToPrint;

		QRectF diagramBB = diagram->border_and_titleblock.borderAndTitleBlockRect();
		// -------------------------------
		// DEBUG: exhaustive dump (INSERT)
		// -------------------------------
		qInfo() << "DEBUG_DUMP: starting diagram dump folio=" << diagram->folioIndex()
		<< "title=\"" << diagram->title() << "\" items=" << diagram->items().count();

		// Iteriere (kurz) über Items und logge Element-spezifische Details
		for (auto itm2 : diagram->items()) {
			if (!itm2) continue;
			QString classname = classNameOf(itm2);

			if (auto el2 = qgraphicsitem_cast<Element*>(itm2)) {
				qInfo() << "DEBUG_EL: Element ptr=" << el2
				<< " sceneCenter=" << el2->sceneBoundingRect().center()
				<< " childCount=" << el2->childItems().count()
				<< " linkedCount=" << el2->linkedElements().count();

				// liste verlinkte Elemente (falls vorhanden)
				for (auto linked : el2->linkedElements()) {
					if (!linked) continue;
					qInfo() << "    -> linkedEl ptr=" << linked
					<< " folio=" << (linked->diagram() ? linked->diagram()->folioIndex() : -1)
					<< " sceneCenter=" << linked->sceneBoundingRect().center();
				}

				// immediate children (Typ + Text falls QGraphicsTextItem)
				for (auto ch : el2->childItems()) {
					if (!ch) continue;
					QString chcn = classNameOf(ch);
					if (auto qti = qgraphicsitem_cast<QGraphicsTextItem*>(ch)) {
						qInfo() << "    child QGraphicsTextItem text=\"" << qti->toPlainText()
						<< "\" bbox=" << qti->boundingRect();
					} else {
						qInfo() << "    child type=" << chcn << " ptr=" << ch;
					}
				}

				// dynamicTextItems prüfen
				for (auto d : el2->dynamicTextItems()) {
					if (!d) continue;
					qInfo() << "    dynamicText infoName=" << d->infoName()
					<< " textFrom=" << d->textFrom()
					<< " plain=\"" << d->toPlainText() << "\" children=" << d->childItems().count();
					for (auto ch : d->childItems()) {
						if (!ch) continue;
						QString chcn = classNameOf(ch);
						qInfo() << "        dynChild type=" << chcn << " ptr=" << ch;
						if (auto qti = qgraphicsitem_cast<QGraphicsTextItem*>(ch)) {
							qInfo() << "           dynChild text=" << qti->toPlainText();
						} else if (auto qst = qgraphicsitem_cast<QGraphicsSimpleTextItem*>(ch)) {
							qInfo() << "           dynChild simple text=" << qst->text();
						}
					}
				}

			} else {
				// nicht-Element-Items (z.B. CrossRefItems auf Diagramm-Ebene)
				if (classname.contains("CrossRef", Qt::CaseInsensitive)) {
					qInfo() << "DEBUG_NON_EL: CrossRef-like item in diagram ptr=" << itm2
					<< " center=" << itm2->sceneBoundingRect().center();
				} else {
					// optional: sehr kurze Meldung für andere Items
					// qInfo() << "DEBUG_ITEM: type=" << classname << " ptr=" << itm2;
				}
			}
		} // end for itm2



		for (auto itm : diagram->items()) {
			if (!itm) continue;

			ReportElement* re = qgraphicsitem_cast<ReportElement*>(itm);
			Element* el = qgraphicsitem_cast<Element*>(itm);

			qInfo() << "ITEM types: itm=" << itm
			<< "isReportElement=" << (re != nullptr)
			<< "isElement=" << (el != nullptr)
			<< "childCount=" << itm->childItems().count();

			if (re) {
				qInfo() << " -> ReportElement type:" << re->elementData().m_type;
			}
			if (el) {
				qInfo() << " -> Element linkedElements count:" << el->linkedElements().count();
			}

			// Falls es kein Element ist, überspringen
			if (!el) continue;

			// Falls vorhanden, prüfe ElementData (wie vorher) - sonst nur linkedElements prüfen
			if (re) {
				// Skip terminals or elements without links (wie vorher)
				if ((re->elementData().m_type == ElementData::Terminal) || el->linkedElements().isEmpty()) {
					qInfo() << "Skipping element (terminal or no links). type=" << re->elementData().m_type
					<< "links=" << el->linkedElements().count();
					continue;
				}
			} else {
				// kein ReportElement: trotzdem skip wenn keine Links
				if (el->linkedElements().isEmpty()) {
					qInfo() << "Skipping element (no links) element ptr=" << el;
					continue;
				}
			}

			// Debug: Loggen, welches Element wir gerade als Ursprung verwenden
			qInfo() << "readXrefList: origin Element ptr=" << el << "linkedCount=" << el->linkedElements().count()
			<< (re ? QString(" (has ReportElement)") : QString(" (no ReportElement)"));

			// linkNumber ++ und xref tmp erzeugen (wie vorher)
			linkNumber++;
			xref tmp;
			tmp.name = QStringLiteral("%1").arg(linkNumber, 4, 10, QLatin1Char('0'));
			tmp.folio = diagram->folioIndex();
			tmp.pdfPage = pagesMap[diagram->folioIndex()];

			// Robustere Positionsermittlung: 1) try slave xref text (child QGraphicsTextItem);
			// 2) else prefer DynamicElementTextItem center (if it's an element label);
			// 3) fallback to element center.
			QPointF chosenScene;
			bool chosen = false;

			// 1) Search dynamic text items for a slave xref child text item (preferred)
			for (auto d : el->dynamicTextItems()) {
				if (!d) continue;

				bool looksLikeLabel =
				(d->textFrom() == DynamicElementTextItem::ElementInfo && d->infoName() == "label") ||
				(d->textFrom() == DynamicElementTextItem::CompositeText && d->compositeText().contains("%{label}"));

				for (auto ch : d->childItems()) {
					if (!ch) continue;
					if (auto qti = qgraphicsitem_cast<QGraphicsTextItem*>(ch)) {
						chosenScene = qti->mapToScene(qti->boundingRect().center());
						chosen = true;
						break;
					} else if (auto qst = qgraphicsitem_cast<QGraphicsSimpleTextItem*>(ch)) {
						chosenScene = qst->mapToScene(qst->boundingRect().center());
						chosen = true;
						break;
					}
				}

				if (chosen) break;

				if (looksLikeLabel) {
					chosenScene = d->mapToScene(d->boundingRect().center());
					chosen = true;
					break;
				}
			}

			// 2) If not chosen yet, search element's child items for QGraphicsTextItem that looks numeric/short
			if (!chosen) {
				for (auto ch : el->childItems()) {
					if (!ch) continue;
					if (auto qti = qgraphicsitem_cast<QGraphicsTextItem*>(ch)) {
						QString txt = qti->toPlainText();
						if (txt.size() <= 8 && txt.contains(QRegExp("\\d"))) {
							chosenScene = qti->mapToScene(qti->boundingRect().center());
							chosen = true;
							break;
						}
					}
				}
			}

			// 3) fallback: element center
			if (!chosen) {
				chosenScene = el->mapToScene(el->boundingRect().center());
			}

			QPointF adjustedOrigin = chosenScene - diagramBB.topLeft();
			tmp.pos.setX(adjustedOrigin.x());
			tmp.pos.setY(adjustedOrigin.y());

			qInfo() << "readXrefList: link" << tmp.name << "chosenScene" << chosenScene << "chosen=" << chosen;

			// target = erstes verlinktes Element
			Element* targetEl = nullptr;
			if (!el->linkedElements().isEmpty()) {
				targetEl = el->linkedElements().first();
			}

			// 1) prefer linkedElements() if present
			if (!el->linkedElements().isEmpty()) {
				targetEl = el->linkedElements().first();
			}

			// 2) else: try to find a CrossRefItem that belongs to / is near this element (diagram-level)
			if (!targetEl) {
				// search radius in scene units (tune if needed)
				const qreal searchRadius = 20.0;

				QRectF elSceneRect = el->sceneBoundingRect();
				QRectF inflated = elSceneRect.adjusted(-searchRadius, -searchRadius, searchRadius, searchRadius);

				CrossRefItem* foundCr = nullptr;

				// first: prefer CrossRefItem that intersects/overlaps the element area
				for (auto it2 : diagram->items()) {
					if (!it2) continue;
					if (auto cr = qgraphicsitem_cast<CrossRefItem*>(it2)) {
						if (inflated.intersects(cr->sceneBoundingRect())) {
							foundCr = cr;
							break;
						}
					}
				}

				// second: if none intersects, pick the nearest CrossRefItem if it's not too far
				if (!foundCr) {
					qreal bestDist = std::numeric_limits<qreal>::infinity();
					QPointF elCenter = el->sceneBoundingRect().center();
					for (auto it2 : diagram->items()) {
						if (!it2) continue;
						if (auto cr = qgraphicsitem_cast<CrossRefItem*>(it2)) {
							qreal d = QLineF(elCenter, cr->sceneBoundingRect().center()).length();
							if (d < bestDist) { bestDist = d; foundCr = cr; }
						}
					}
					// ignore if the nearest is very far away
					if (bestDist > 100.0) foundCr = nullptr;
				}

				// if we found a CrossRefItem, try to resolve the target element by proximity to that CrossRef
				if (foundCr) {
					QPointF crCenter = foundCr->sceneBoundingRect().center();

					// If we didn't find a good chosenScene yet, use CR center as origin marker
					if (!chosen) {
						chosenScene = crCenter;
						chosen = true;
					}

					// find the nearest element (other than current) to the CrossRef center
					qreal bestDEl = std::numeric_limits<qreal>::infinity();
					for (auto itEl : diagram->items()) {
						if (!itEl) continue;
						if (auto otherEl = qgraphicsitem_cast<Element*>(itEl)) {
							if (otherEl == el) continue;
							qreal d = QLineF(crCenter, otherEl->sceneBoundingRect().center()).length();
							if (d < bestDEl) { bestDEl = d; targetEl = otherEl; }
						}
					}
					if (bestDEl > 150.0) {
						// too far -> give up
						targetEl = nullptr;
					}
				}
			}

			// 3) Fallback (if nothing found earlier): leave target empty
			if (targetEl) {
				tmp.targetFolio = targetEl->diagram()->folioIndex();
				QRectF targetDiagramBB = targetEl->diagram()->border_and_titleblock.borderAndTitleBlockRect();
				QPointF targetScene = targetEl->mapToScene(targetEl->boundingRect().center());
				QPointF adjustedTarget = targetScene - targetDiagramBB.topLeft();
				tmp.targetPos.setX(adjustedTarget.x());
				tmp.targetPos.setY(adjustedTarget.y());
			} else {
				tmp.targetFolio = -1;
				tmp.targetPos = QPointF(0,0);
			}


			xrefList.append(tmp);
		} // end for items

	}

	// set correct target-PDF-Page in List:
	if (!xrefList.isEmpty()) {
		for (int i = 0; i < xrefList.count(); ++i) {
			int tf = xrefList[i].targetFolio;
			if (pagesMap.contains(tf)) {
				xrefList[i].targetPdfPage = pagesMap[tf];
			} else {
				xrefList[i].targetPdfPage = -1;
			}
		}
	}
}

/* --------------------------------------------------------------------------- */
/* requestPaint: erzeugt Painter, liest XRefs, ruft printDiagram für jeden     */
/* Diagramm auf (printDiagram inkrementiert Seiten & zeichnet Debug-Rects)    */
/* --------------------------------------------------------------------------- */

// --- ersetze bestehende drawCandidateRects(...) durch folgenden Code ---
void ProjectPrintWindow::drawCandidateRects(
	QPainter *painter,
	Diagram *diagram,
	bool fitMode,
	const QRect& printedRectPixels,
	const QRect& sourceDiagramRect)
{
	if (!painter || !diagram) return;

	QRectF diagramBB = diagram->border_and_titleblock.borderAndTitleBlockRect();
	qreal diag_w = diagramBB.width();
	qreal diag_h = diagramBB.height();
	qreal used_w = printedRectPixels.width();
	qreal used_h = printedRectPixels.height();

	auto mapSceneToDevice = [&](const QPointF &sceneCenter)->QPointF {
		QPointF rel = sceneCenter - diagramBB.topLeft();
		if (fitMode) {
			if (diag_w <= 0.0 || diag_h <= 0.0) return rel;
			qreal scale = qMin(used_w / diag_w, used_h / diag_h);
			qreal dx = (used_w - diag_w * scale) / 2.0;
			qreal dy = (used_h - diag_h * scale) / 2.0;
			return QPointF(dx + rel.x() * scale, dy + rel.y() * scale);
		} else {
			QRectF tile = QRectF(sourceDiagramRect);
			return QPointF(rel.x() - tile.left(), rel.y() - tile.top());
		}
	};

	const qreal hx = 8.0;
	const qreal hy = 6.0;

	// helper: draw translucent rect with pen + optional log
	auto drawRectWithLog = [&](const QPointF &devCenter, const QColor &penColor, const QColor &fillColor, const QString &logText = QString()) {
		QRectF r(devCenter.x()-hx, devCenter.y()-hy, hx*2, hy*2);
		painter->save();
		QPen pen(penColor);
		pen.setWidthF(0.6);
		painter->setPen(pen);
		painter->setBrush(QBrush(fillColor));
		painter->drawRect(r);
		painter->restore();
		if (!logText.isEmpty()) {
			qInfo() << logText << "dev=" << devCenter << "rect=" << r;
		}
	};

	// recursive walker: find QGraphicsTextItem / QGraphicsSimpleTextItem descendants and mark them (cyan/yellow)
	std::function<void(QGraphicsItem*)> markTextDescendants;
	markTextDescendants = [&](QGraphicsItem *parentItem) {
		for (auto ch : parentItem->childItems()) {
			if (!ch) continue;
			if (auto qti = qgraphicsitem_cast<QGraphicsTextItem*>(ch)) {
				QPointF sc = qti->sceneBoundingRect().center();
				QPointF dev = mapSceneToDevice(sc);
				drawRectWithLog(dev, Qt::cyan, QColor(0,255,255,64),
								QString("TEXT_ITEM (QGraphicsTextItem) text=\"%1\"").arg(qti->toPlainText()));
			} else if (auto qst = qgraphicsitem_cast<QGraphicsSimpleTextItem*>(ch)) {
				QRectF bb = qst->boundingRect();
				QPointF sc = qst->mapToScene(bb.center());
				QPointF dev = mapSceneToDevice(sc);
				drawRectWithLog(dev, Qt::yellow, QColor(255,200,0,64),
								QString("SIMPLE_TEXT text=\"%1\"").arg(qst->text()));
			}
			// recurse deeper
			markTextDescendants(ch);
		}
	};

	for (auto itm : diagram->items()) {
		if (!itm) continue;

		// ReportElement => blau
		if (auto re = qgraphicsitem_cast<ReportElement*>(itm)) {
			QPointF sc = re->sceneBoundingRect().center();
			QPointF dev = mapSceneToDevice(sc);
			drawRectWithLog(dev, Qt::blue, QColor(0,0,255,64));
		}

		// Element => rot (center)
		if (auto el = qgraphicsitem_cast<Element*>(itm)) {
			QPointF sc = el->sceneBoundingRect().center();
			QPointF dev = mapSceneToDevice(sc);
			drawRectWithLog(dev, Qt::red, QColor(255,0,0,64), QString("ELEMENT center"));

			// 1) Durchsuche DynamicElementTextItem-Objekte des Elements und deren Kinder (wichtig!)
			for (auto d : el->dynamicTextItems()) {
				if (!d) continue;
				// mark the dynamic text center (grün)
				QPointF sc2 = d->sceneBoundingRect().center();
				QPointF dev2 = mapSceneToDevice(sc2);
				drawRectWithLog(dev2, Qt::green, QColor(0,255,0,64),
								QString("DYNAMIC_TEXT center infoName=\"%1\" plain=\"%2\"")
								.arg(d->infoName()).arg(d->toPlainText()));

				// mark any child text items under the dynamic text (this is where m_slave_Xref_item usually sits)
				markTextDescendants(d);
			}

			// 2) Durchsuche Element->childItems rekursiv (falls XRef als Kind irgendwo hängt)
			markTextDescendants(el);

			// 3) Zusätzlich: speziell nach CrossRefItem suchen (magenta)
			for (auto ch : el->childItems()) {
				if (!ch) continue;
				if (auto cr = qgraphicsitem_cast<CrossRefItem*>(ch)) {
					QPointF sc3 = cr->sceneBoundingRect().center();
					QPointF dev3 = mapSceneToDevice(sc3);
					painter->save();
					QPen pen(Qt::magenta);
					pen.setStyle(Qt::DashLine);
					pen.setWidthF(0.6);
					painter->setPen(pen);
					painter->setBrush(QBrush(QColor(255,0,255,64)));
					painter->drawRect(QRectF(dev3.x()-hx, dev3.y()-hy, hx*2, hy*2));
					painter->restore();
					qInfo() << "CrossRefItem dev=" << dev3;
				}
			}
		} // Element*
	} // items loop
}



/* --------------------------------------------------------------------------- */
/* printDiagram: rendert Diagramm, verwaltet Seitenzählung; nach jedem Render  */
/* wird addPdfLinks/addPdfTargets mit der tatsächlichen page aufgerufen        */
/* --------------------------------------------------------------------------- */
void ProjectPrintWindow::requestPaint()
{
	qInfo("void ProjectPrintWindow::requestPaint()");

	// Falls kein Projekt/Printer oder nichts zu tun: nichts tun
	if (!m_project || !m_printer) return;
	if (!m_project->diagrams().count()) return;

	// Erstelle Painter für den Preview/Printer
	QPainter painter(m_printer);
	if (!painter.isActive()) {
		qWarning() << "requestPaint: QPainter konnte nicht aktiv werden";
		return;
	}

	// Prepare page counting + xref list
	uint currentPage = 0;
	QList<xref> XRefList;
	readXrefList(XRefList);

	bool first = true;
	// Für jedes ausgewählte Diagramm: rendern (printDiagram inkrementiert currentPage)
	for (auto diagram : selectedDiagram())
	{
		// printDiagram kümmert sich um newPage() und Seitenzählung
		printDiagram(diagram,
					 ui->m_fit_in_page_cb->isChecked(),
					 &painter,
			   m_printer,
			   currentPage,
			   XRefList,
			   first);

		// Debug: nach dem Rendern zeichnen wir die sichtbaren Link-/Target-Rects
		// (printDiagram ruft addPdfLinks/addPdfTargets bei Bedarf bereits auf,
		//  aber hier stellen wir sicher, dass für den Preview ebenfalls sichtbar gezeichnet wird)
		addPdfLinks(&painter, XRefList, currentPage);
		addPdfTargets(&painter, XRefList, currentPage);
	}
}

void ProjectPrintWindow::printDiagram(
	Diagram *diagram,
	bool fit_page,
	QPainter *painter,
	QPrinter *printer,
	uint &currentPage,
	const QList<xref>& xreflist,
	bool &firstPage)
{
	qInfo("void ProjectPrintWindow::printDiagram(...)");
	//// Prepare the print ////
	diagram->deselectAll();
	QList<QGraphicsItem *> focusable_items;
	for (auto qgi : diagram->items()) {
		if (qgi->flags() & QGraphicsItem::ItemIsFocusable) {
			focusable_items << qgi;
			qgi->setFlag(QGraphicsItem::ItemIsFocusable, false);
		}
	}
	for (auto view : diagram->views()) {
		view->setInteractive(false);
	}
	auto option = exportProperties();
	saveReloadDiagramParameters(diagram, option, true);
	//// Prepare end ////

	auto full_page = printer->fullPage();
	auto diagram_rect = diagramRect(diagram, option); // in diagram units

	if (fit_page) {
		// Produce exactly one PDF page for that diagram (fit to page)
		currentPage++;
		if (!firstPage) {
			printer->newPage();
		} else {
			firstPage = false;
		}

		// Render the whole diagram fitted into the page
		diagram->render(painter, QRectF(), diagram_rect, Qt::KeepAspectRatio);

		// Compute printed area in pixels for mapping (nur einmal deklarieren)
		QRect printedRectPixels = full_page ? printer->pageLayout().fullRectPixels(printer->resolution())
		: printer->pageLayout().paintRectPixels(printer->resolution());

		// draw debug rects
		drawCandidateRects(painter, diagram, true, printedRectPixels, diagram_rect);

		// Add / draw pdf-links & targets for this page (device mapping inside functions)
		addPdfLinks(painter, xreflist, currentPage, true, printedRectPixels, diagram_rect);
		addPdfTargets(painter, xreflist, currentPage, true, printedRectPixels, diagram_rect);


	} else {
		// Tiled mode: print across multiple pages
		auto printed_rect = full_page ? printer->paperRect(QPrinter::Millimeter) : printer->pageRect(QPrinter::Millimeter);
		auto used_width  = printed_rect.width();
		auto used_height = printed_rect.height();
		auto h_pages_count = horizontalPagesCount(diagram, option, full_page);
		auto v_pages_count = verticalPagesCount(diagram, option, full_page);

		QVector<QVector<QRect>> page_grid;
		qreal y_offset = 0;
		for (auto i=0 ; i<v_pages_count ; ++i)
		{
			page_grid << QVector<QRect>();
			qreal x_offset = 0;
			for (auto j=0 ; j<h_pages_count ; ++j)
			{
				page_grid.last() << QRect(QPoint(x_offset, y_offset),
										  QSize(qMin(used_width, diagram_rect.width() - x_offset),
												qMin(used_height, diagram_rect.height() - y_offset)));
				x_offset += used_width;
			}
			y_offset += used_height;
		}

		QVector<QRect> page_to_print;
		for (auto i=0 ; i < v_pages_count ; ++i) {
			for (int j=0 ; j < h_pages_count ; ++j) {
				page_to_print << page_grid.at(i).at(j);
			}
		}

		// For each tile/page we render and then add links for that specific page
		for (auto page : page_to_print)
		{
			currentPage++;
			if (!firstPage) {
				printer->newPage();
			} else {
				firstPage = false;
			}

			// page is a QRect in diagram units corresponding to the source rect to render into this sheet
			diagram->render(painter, QRect(QPoint(0,0), page.size()), page.translated(diagram_rect.topLeft()), Qt::KeepAspectRatio);

			// device page canvas size
			QRect printedRectPixels = QRect(0, 0, page.width(), page.height());

			// draw debug rects for this tile/page
			drawCandidateRects(painter, diagram, false, printedRectPixels, page);

			// Draw the pdf-links/targets for this tile/page
			addPdfLinks(painter, xreflist, currentPage, false, printedRectPixels, page);
			addPdfTargets(painter, xreflist, currentPage, false, printedRectPixels, page);

		}
	}

	//// Print is finished, restore diagram and graphics item properties
	for (auto view : diagram->views()) {
		view->setInteractive(true);
	}
	for (auto qgi : focusable_items) {
		qgi->setFlag(QGraphicsItem::ItemIsFocusable, true);
	}
	saveReloadDiagramParameters(diagram, option, false);
}

/* --------------------------------------------------------------------------- */
/* Unterstützende Helfer (diagramRect, page counts, exportProperties, UI)     */
/* (unverändert gegenüber originalem Verhalten, nur an den Aufruf angepasst)   */
/* --------------------------------------------------------------------------- */

QRect ProjectPrintWindow::diagramRect(Diagram *diagram, const ExportProperties &option) const
{
	auto diagram_rect = diagram->border_and_titleblock.borderAndTitleBlockRect();
	if (!option.draw_titleblock) {
		auto titleblock_height = diagram->border_and_titleblock.titleBlockRect().height();
		diagram_rect.setHeight(diagram_rect.height() - titleblock_height);
	}

	//Adjust the border of diagram to 1px (width of the line)
	diagram_rect.adjust(0,0,1,1);

	return (diagram_rect.toAlignedRect());
}

int ProjectPrintWindow::horizontalPagesCount(
	Diagram *diagram, const ExportProperties &option, bool full_page) const
	{
		QRect printable_area;
		printable_area =
		full_page ?
		m_printer->pageLayout().fullRectPixels(m_printer->resolution()) :
		m_printer->pageLayout().paintRectPixels(m_printer->resolution());
		QRect diagram_rect = diagramRect(diagram, option);

		int h_pages_count = int(ceil(qreal(diagram_rect.width()) / qreal(printable_area.width())));
		return(h_pages_count);
	}

	int ProjectPrintWindow::verticalPagesCount(
		Diagram *diagram, const ExportProperties &option, bool full_page) const
		{
			QRect printable_area;
			printable_area =
			full_page ?
			m_printer->pageLayout().fullRectPixels(m_printer->resolution()) :
			m_printer->pageLayout().paintRectPixels(m_printer->resolution());
			QRect diagram_rect = diagramRect(diagram, option);

			int v_pages_count = int(ceil(qreal(diagram_rect.height()) / qreal(printable_area.height())));
			return(v_pages_count);
		}

		ExportProperties ProjectPrintWindow::exportProperties() const
		{
			ExportProperties exp;
			exp.draw_border             = ui->m_draw_border_cb->isChecked();
			exp.draw_titleblock         = ui->m_draw_titleblock_cb->isChecked();
			exp.draw_terminals          = ui->m_draw_terminal_cb->isChecked();
			exp.draw_colored_conductors = ui->m_keep_conductor_color_cb->isChecked();
			exp.draw_grid = false;

			return exp;
		}

		void ProjectPrintWindow::setUpDiagramList()
		{
			auto layout = new QVBoxLayout();
			auto widget = new QWidget();
			widget->setLayout(layout);
			widget->setMinimumSize(170, 0);
			widget->setMaximumSize(470, 10000);
			widget->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
			ui->m_diagram_list->setWidget(widget);

			for (auto diagram : m_project->diagrams())
			{
				auto title = diagram->title();
				if (title.isEmpty()) {
					title = tr("Folio sans titre");
				}

				auto checkbox = new QCheckBox(title);
				checkbox->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum));
				checkbox->setChecked(true);
				layout->addWidget(checkbox, 0, Qt::AlignLeft | Qt::AlignTop);
				connect(checkbox, &QCheckBox::clicked, m_preview, &QPrintPreviewWidget::updatePreview);
				m_diagram_list_hash.insert(diagram, checkbox);
			}
			layout->addStretch();
		}

		QString ProjectPrintWindow::settingsSectionName(const QPrinter *printer)
		{
			QPrinter::OutputFormat printer_format = printer -> outputFormat();
			if (printer_format == QPrinter::NativeFormat) {
				return(printer -> printerName().replace(" ", "_"));
			} else if (printer_format == QPrinter::PdfFormat) {
				return("QET_PDF_Printing");
			}
			return(QString());
		}

		void ProjectPrintWindow::loadPageSetupForCurrentPrinter()
		{
			QSettings settings;
			QString	  printer_section = settingsSectionName(m_printer);

			while (! settings.group().isEmpty()) settings.endGroup();
			settings.beginGroup("printers");
			if (! settings.childGroups().contains(printer_section))
			{
				settings.endGroup();
				return;
			}

			settings.beginGroup(printer_section);
			if (settings.contains("orientation"))
			{
				QString value = settings.value("orientation", "landscape").toString();
				m_printer->setPageOrientation(
					value == "landscape" ? QPageLayout::Landscape :
					QPageLayout::Portrait);
			}
			if (settings.contains("papersize"))
			{
				int value = settings.value("papersize", QPageSize::A4).toInt();
				if (value == QPageSize::Custom)
				{
					bool w_ok, h_ok;
					int	 w = settings.value("customwidthmm", -1).toInt(&w_ok);
					int	 h = settings.value("customheightmm", -1).toInt(&h_ok);
					if (w_ok && h_ok && w != -1 && h != -1)
					{
						m_printer->setPageSize(QPageSize(
							QSizeF(w, h),
														 QPageSize::Millimeter,
									   "Custom",
									   QPageSize::FuzzyMatch));
					}
				}
				else if (value < QPageSize::Custom)
				{
					QPageSize var;
					var.id(value);
					m_printer->setPageSize(var);
				}
			}

			qreal margins[4];
			margins[0] = m_printer->pageLayout().margins().left();
			margins[1] = m_printer->pageLayout().margins().top();
			margins[2] = m_printer->pageLayout().margins().right();
			margins[3] = m_printer->pageLayout().margins().bottom();
			QStringList margins_names(
				QStringList() << "left"
				<< "top"
				<< "right"
				<< "bottom");
			for (int i = 0; i < 4; ++i)
			{
				bool  conv_ok;
				qreal value = settings.value("margin" % margins_names.at(i), -1.0)
				.toReal(&conv_ok);
				if (conv_ok && value != -1.0) margins[i] = value;
			}
			m_printer->setPageMargins(
				QMarginsF(margins[0], margins[1], margins[2], margins[3]),
									  QPageLayout::Millimeter);
			m_printer->setFullPage(
				settings.value("fullpage", "false").toString() == "true");

			settings.endGroup();
			settings.endGroup();
		}

		void ProjectPrintWindow::savePageSetupForCurrentPrinter()
		{
			QSettings settings;
			QString printer_section = settingsSectionName(m_printer);

			while (!settings.group().isEmpty()) settings.endGroup();
			settings.beginGroup("printers");
			settings.beginGroup(printer_section);

			settings.setValue(
				"orientation",
				m_printer->pageLayout().orientation() == QPageLayout::Portrait ?
				"portrait" :
				"landscape");
			settings.setValue(
				"papersize",
				int(m_printer->pageLayout().pageSize().id()));
			if (m_printer->pageLayout().pageSize().id() == QPageSize::Custom)
			{
				QSizeF size =
				m_printer->pageLayout().pageSize().size(QPageSize::Millimeter);
				settings.setValue("customwidthmm", size.width());
				settings.setValue("customheightmm", size.height());
			}
			else
			{
				settings.remove("customwidthmm");
				settings.remove("customheightmm");
			}
			settings.setValue("marginleft", m_printer->pageLayout().margins().left());
			settings.setValue("margintop", m_printer->pageLayout().margins().top());
			settings.setValue("marginright", m_printer->pageLayout().margins().right());
			settings.setValue(
				"marginbottom",
				m_printer->pageLayout().margins().bottom());
			settings.setValue("fullpage", m_printer->fullPage() ? "true" : "false");
			settings.endGroup();
			settings.endGroup();
			settings.sync();
		}

		void ProjectPrintWindow::saveReloadDiagramParameters(Diagram *diagram, const ExportProperties &options, bool save)
		{
			static ExportProperties state_exportProperties;

			if (save) {
				state_exportProperties = diagram -> applyProperties(options);
			} else {
				diagram -> applyProperties(state_exportProperties);
			}
		}

		QList<Diagram *> ProjectPrintWindow::selectedDiagram() const
		{
			QList<Diagram *> selected_diagram;
			for (auto diagram : m_project->diagrams()) {
				auto cb = m_diagram_list_hash[diagram];
				if (cb && cb->isChecked()) {
					selected_diagram << diagram;
				}
			}

			return selected_diagram;
		}

		void ProjectPrintWindow::exportToPDF()
		{
			auto file_name = QFileDialog::getSaveFileName(this, tr("Exporter sous : "), m_printer->outputFileName(), tr("Fichier (*.pdf)"));
			if (file_name.isEmpty()) {
				return;
			}
			m_printer->setOutputFileName(file_name);
			m_printer->setOutputFormat(QPrinter::PdfFormat);
			print();
		}

		void ProjectPrintWindow::on_m_draw_border_cb_clicked()          { m_preview->updatePreview(); }
		void ProjectPrintWindow::on_m_draw_titleblock_cb_clicked()      { m_preview->updatePreview(); }
		void ProjectPrintWindow::on_m_keep_conductor_color_cb_clicked() { m_preview->updatePreview(); }
		void ProjectPrintWindow::on_m_draw_terminal_cb_clicked()        { m_preview->updatePreview(); }
		void ProjectPrintWindow::on_m_fit_in_page_cb_clicked()          { m_preview->updatePreview(); }
		void ProjectPrintWindow::on_m_use_full_page_cb_clicked()
		{
			m_printer->setFullPage(ui->m_use_full_page_cb->isChecked());
			m_preview->updatePreview();
		}

		void ProjectPrintWindow::on_m_zoom_out_action_triggered() {
			m_preview->zoomOut(4.0/3.0);
		}

		void ProjectPrintWindow::on_m_zoom_in_action_triggered() {
			m_preview->zoomIn(4.0/3.0);
		}

		void ProjectPrintWindow::on_m_adjust_width_action_triggered() {
			m_preview->fitToWidth();
		}

		void ProjectPrintWindow::on_m_adjust_page_action_triggered() {
			m_preview->fitInView();
		}

		void ProjectPrintWindow::on_m_landscape_action_triggered() {
			m_preview->setLandscapeOrientation();
		}

		void ProjectPrintWindow::on_m_portrait_action_triggered() {
			m_preview->setPortraitOrientation();
		}

		void ProjectPrintWindow::on_m_first_page_action_triggered() {
			m_preview->setCurrentPage(1);
		}

		void ProjectPrintWindow::on_m_previous_page_action_triggered()
		{
			auto previous_page = m_preview->currentPage() - 1;
			m_preview->setCurrentPage(std::max(previous_page, 0));
		}

		void ProjectPrintWindow::on_m_next_page_action_triggered()
		{
			auto next_page = m_preview->currentPage() + 1;
			m_preview->setCurrentPage(std::min(next_page, m_preview->pageCount()));
		}

		void ProjectPrintWindow::on_m_last_page_action_triggered() {
			m_preview->setCurrentPage(m_preview->pageCount());
		}

		void ProjectPrintWindow::on_m_display_single_page_action_triggered() {
			m_preview->setSinglePageViewMode();
		}

		void ProjectPrintWindow::on_m_display_two_page_action_triggered() {
			m_preview->setFacingPagesViewMode();
		}

		void ProjectPrintWindow::on_m_display_all_page_action_triggered() {
			m_preview->setAllPagesViewMode();
		}

		void ProjectPrintWindow::on_m_page_setup_triggered()
		{
			QPageSetupDialog d(m_printer, this);
			if (d.exec() == QDialog::Accepted) {
				m_preview->updatePreview();
			}
		}

		void ProjectPrintWindow::on_m_check_all_pb_clicked()
		{
			for (auto cb : m_diagram_list_hash.values()) {
				cb->setChecked(true);
			}
			m_preview->updatePreview();
		}

		void ProjectPrintWindow::on_m_uncheck_all_clicked()
		{
			for (auto cb : m_diagram_list_hash.values()) {
				cb->setChecked(false);
			}
			m_preview->updatePreview();
		}

		void ProjectPrintWindow::print()
		{
			// Ensure preview populates correctly (synchronous)
			m_preview->updatePreview();
			QCoreApplication::processEvents();
			m_preview->print();
			savePageSetupForCurrentPrinter();
			this->close();
		}

		void ProjectPrintWindow::on_m_date_cb_userDateChanged(const QDate &date)
		{
			auto index = ui->m_date_from_cb->currentIndex();
			// 0 = all date
			// 1 = from the date
			// 2 = at the date

			if (index) { on_m_uncheck_all_clicked();  }
			else       { on_m_check_all_pb_clicked(); }

			for (auto diagram : m_diagram_list_hash.keys())
			{
				auto diagram_date = diagram->border_and_titleblock.date();
				if ( (index == 1 && diagram_date >= date) ||
					(index == 2 && diagram_date == date) )
					m_diagram_list_hash.value(diagram)->setChecked(true);
			}

			m_preview->updatePreview();
		}

		void ProjectPrintWindow::on_m_date_from_cb_currentIndexChanged(int index)
		{
			Q_UNUSED(index)

			ui->m_date_cb->setEnabled(index);
			ui->m_apply_date_pb->setEnabled(index);
			on_m_date_cb_userDateChanged(ui->m_date_cb->date());

		}

		void ProjectPrintWindow::on_m_apply_date_pb_clicked() {
			on_m_date_cb_userDateChanged(ui->m_date_cb->date());
		}

		/* --------------------------------------------------------------------------- */
		/* addPdfLinks / addPdfTargets: zeichnen Debug-Rechtecke, mappen auf Device-Px */
		/* --------------------------------------------------------------------------- */

		void ProjectPrintWindow::addPdfLinks(
			QPainter *painter,
			const QList<xref>& list,
			const uint page,
			bool fitMode,
			const QRect& printedRectPixels,
			const QRect& sourceDiagramRect)
		{
			// Page rectangle in points for PDF coordinate conversion
			QRectF pageRectPoints = m_printer->pageRect(QPrinter::Point);

			// Determine DPI for conversion; fall back to sensible default
			double dpi = m_printer ? m_printer->resolution() : (painter ? painter->device()->logicalDpiY() : 300.0);
			if (dpi <= 0.0) dpi = 300.0;
			double pointsPerDeviceUnit = 72.0 / dpi;

			for (uint i = 0; i < (uint)list.count(); ++i) {
				if (list[i].pdfPage == (int)page) {
					QPointF diagramPos(list[i].pos.x(), list[i].pos.y());

					// Compute device coordinates (pixels) for the link center:
					QPointF deviceCenter(0,0);

					if (fitMode) {
						QRectF diagramSrc = QRectF(sourceDiagramRect); // floating precision
						if (diagramSrc.width() <= 0.0 || diagramSrc.height() <= 0.0) {
							deviceCenter = diagramPos;
						} else {
							qreal used_w = printedRectPixels.width();
							qreal used_h = printedRectPixels.height();
							qreal diag_w  = diagramSrc.width();
							qreal diag_h  = diagramSrc.height();
							qreal scale = qMin(used_w / diag_w, used_h / diag_h);
							qreal dx = (used_w - diag_w * scale) / 2.0;
							qreal dy = (used_h - diag_h * scale) / 2.0;
							deviceCenter.setX(dx + diagramPos.x() * scale);
							deviceCenter.setY(dy + diagramPos.y() * scale);
						}
					} else {
						// Tiled mode: use sourceDiagramRect as tile origin
						QRectF tile = QRectF(sourceDiagramRect);
						deviceCenter.setX(diagramPos.x() - tile.left());
						deviceCenter.setY(diagramPos.y() - tile.top());
					}

					QRectF linkArea(deviceCenter.x() - 10.0, deviceCenter.y() - 7.5, 20.0, 15.0);

					// draw visible debug rectangle (red, translucent)
					if (painter) {
						painter->save();
						QPen pen(Qt::red);
						pen.setWidthF(0.6);
						painter->setPen(pen);
						QColor fill(255, 0, 0, 64);
						painter->setBrush(QBrush(fill));
						painter->drawRect(linkArea);
						painter->restore();
					}

					qInfo() << "addPdfLinks: page" << page << "link" << list[i].name << "diagramPos=" << diagramPos << "deviceCenter=" << deviceCenter;
				}
			}
		}

		void ProjectPrintWindow::addPdfTargets(
			QPainter *painter,
			const QList<xref>& list,
			const uint page,
			bool fitMode,
			const QRect& printedRectPixels,
			const QRect& sourceDiagramRect)
		{
			for (uint i = 0; i < (uint)list.count(); ++i) {
				if (list[i].targetPdfPage == (int)page) {
					QPointF diagramTarget(list[i].targetPos.x(), list[i].targetPos.y());
					QPointF deviceCenter(0,0);

					if (fitMode) {
						QRectF diagramSrc = QRectF(sourceDiagramRect);
						if (diagramSrc.width() <= 0.0 || diagramSrc.height() <= 0.0) {
							deviceCenter = diagramTarget;
						} else {
							qreal used_w = printedRectPixels.width();
							qreal used_h = printedRectPixels.height();
							qreal diag_w  = diagramSrc.width();
							qreal diag_h  = diagramSrc.height();
							qreal scale = qMin(used_w / diag_w, used_h / diag_h);
							qreal dx = (used_w - diag_w * scale) / 2.0;
							qreal dy = (used_h - diag_h * scale) / 2.0;
							deviceCenter.setX(dx + diagramTarget.x() * scale);
							deviceCenter.setY(dy + diagramTarget.y() * scale);
						}
					} else {
						QRectF tile = QRectF(sourceDiagramRect);
						deviceCenter.setX(diagramTarget.x() - tile.left());
						deviceCenter.setY(diagramTarget.y() - tile.top());
					}

					QRectF tgtArea(deviceCenter.x() - 10.0, deviceCenter.y() - 7.5, 20.0, 15.0);

					if (painter) {
						painter->save();
						QPen pen(Qt::yellow);
						pen.setStyle(Qt::DashLine);
						pen.setWidthF(0.6);
						painter->setPen(pen);
						QColor fill(255, 200, 0, 48);
						painter->setBrush(QBrush(fill));
						painter->drawRect(tgtArea);
						painter->restore();
					}

					qInfo() << "addPdfTargets: page" << page << "target" << list[i].name << "diagramTarget=" << diagramTarget << "deviceCenter=" << deviceCenter;
				}
			}
		}
