/*
	Copyright 2006-2026 The QElectroTech Team
	This file is part of QElectroTech.

	QElectroTech is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	QElectroTech is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "crossrefitem.h"

#include <QTimer>
#include "../qetproject.h"
#include "../autoNum/assignvariables.h"
#include "../diagram.h"
#include "../diagramposition.h"
#include "../qetapp.h"
#include "dynamicelementtextitem.h"
#include "element.h"
#include "elementtextitemgroup.h"
#include "qgraphicsitemutility.h"
#include "terminal.h"
#include "../properties/elementdata.h"

//define the height of the header.
static int header = 5;
//define the minimal height of the cross (without header)
static int cross_min_height = 33;

/**
	@brief CrossRefItem::CrossRefItem
	@param elmt : element to display the cross ref
*/
CrossRefItem::CrossRefItem(Element *elmt) :
	QGraphicsObject(elmt),
	m_element(elmt)
{init();}

/**
	@brief CrossRefItem::CrossRefItem
	@param elmt : element to display the cross ref
	@param text : If the Xref must be displayed under a text, the text.
*/
CrossRefItem::CrossRefItem(Element *elmt, DynamicElementTextItem *text) :
	QGraphicsObject(text),
	m_element (elmt),
	m_text(text)
{init();}

/**
	@brief CrossRefItem::CrossRefItem
	@param elmt : element to display the cross ref
	@param group : If the Xref must be displayed under a group, the group.
*/
CrossRefItem::CrossRefItem(Element *elmt, ElementTextItemGroup *group) :
	QGraphicsObject(group),
	m_element(elmt),
	m_group(group)
{init();}

/**
	@brief CrossRefItem::~CrossRefItem
	Default destructor
*/
CrossRefItem::~CrossRefItem()
{}

/**
	@brief CrossRefItem::init
	init this Xref
*/
void CrossRefItem::init()
{
	if(!m_element->diagram())
	{
		qDebug() << "CrossRefItem constructor" << "element is not in a diagram";
		return;
	}
	
	QETProject *project = m_element->diagram()->project();
	connect(project, &QETProject::XRefPropertiesChanged, this, &CrossRefItem::updateProperties);
	
	m_properties = m_element->diagram()->project()->defaultXRefProperties(m_element->kindInformations()["type"].toString());
	setAcceptHoverEvents(true);
	
	setUpConnection();
	linkedChanged();
	updateLabel();
}

/**
	@brief CrossRefItem::setUpConnection
	Set up several connection to keep up to date the Xref
*/
void CrossRefItem::setUpConnection()
{
	for(const QMetaObject::Connection& c : m_update_connection)
		disconnect(c);
	
	m_update_connection.clear();
	QETProject *project = m_element->diagram()->project();
	bool set=false;
		
	if(m_properties.snapTo() == XRefProperties::Label && (m_text || m_group)) //Snap to label and parent is a text or a group
		set=true;
	else if(m_properties.snapTo() == XRefProperties::Bottom && !m_text && !m_group) //Snap to bottom of element and parent is the element itself
	{
		m_update_connection << connect(m_element, SIGNAL(yChanged()),
					       this, SLOT(autoPos()));
		m_update_connection << connect(m_element, SIGNAL(rotationChanged()),
					       this, SLOT(autoPos()));
		set=true;
	}

	if(set)
	{
		m_update_connection
				<< connect(project,
					       &QETProject::projectDiagramsOrderChanged,
					       this, &CrossRefItem::updateLabel);
		m_update_connection << connect(project,
					       &QETProject::diagramRemoved,
					       this, &CrossRefItem::updateLabel);
		m_update_connection << connect(m_element,
					       &Element::linkedElementChanged,
					       this, &CrossRefItem::linkedChanged);

		auto diagram_ = dynamic_cast<Diagram *>(this->scene());
		auto formula_ = m_properties.masterLabel();
		if (diagram_ &&
			formula_.contains("%F"))
		{
			m_update_connection << connect(diagram_ , &Diagram::diagramInformationChanged,
										   this, &CrossRefItem::updateLabel);
		}
		linkedChanged();
		updateLabel();
	}
}

/**
	@brief CrossRefItem::boundingRect
	@return the bounding rect of this item
*/
QRectF CrossRefItem::boundingRect() const
{
	return m_bounding_rect;
}

/**
	@brief CrossRefItem::shape
	@return the shape of this item
*/
QPainterPath CrossRefItem::shape() const{
	return m_shape_path;
}

/**
	@brief CrossRefItem::elementPositionText
	@param elmt
	@param add_prefix
	@return the string corresponding to the position of elmt in the diagram.
	if add_prefix is true,
	prefix (for power and delay contact) is added to the position text.
*/
QString CrossRefItem::elementPositionText(
		const Element *elmt, const bool &add_prefix) const
{
	autonum::sequentialNumbers seq;
	QString txt = autonum::AssignVariables::formulaToLabel(
				m_properties.masterLabel(),
				seq, elmt->diagram(),
				elmt);

	if (add_prefix)
	{
		if      (elmt->kindInformations()["type"].toString() == "power")
			txt.prepend(m_properties.prefix("power"));
		else if (elmt->kindInformations()["type"].toString().contains("delay"))
			txt.prepend(m_properties.prefix("delay"));
		else if (elmt->kindInformations()["state"].toString() == "SW")
			txt.prepend(m_properties.prefix("switch"));
	}
	return txt;
}

/**
	@brief CrossRefItem::updateProperties
	update the current properties
*/
void CrossRefItem::updateProperties()
{
	XRefProperties xrp = m_element->diagram()->project()->defaultXRefProperties(m_element->kindInformations()["type"].toString());
	
	if (m_properties != xrp)
	{
		m_properties = xrp;
		hide();
		if(m_properties.snapTo() == XRefProperties::Label && (m_text || m_group)) //Snap to label and parent is text or group
			show();
		else if((m_properties.snapTo() == XRefProperties::Bottom && !m_text && !m_group)) //Snap to bottom of element is the parent
			show();
		
		setUpConnection();
		updateLabel();
	}
}

/**
	@brief CrossRefItem::updateLabel
	Update the content of the item
*/
void CrossRefItem::updateLabel()
{
		//init the shape and bounding rect
	m_shape_path    = QPainterPath();
	prepareGeometryChange();
	m_bounding_rect = QRectF();

	// Build geometry and m_hovered_contacts_map using a QImage-backed
	// painter so font metrics match the screen painter in paint().
	// m_update_map=true allows the draw functions to populate the map;
	// paint() calls them with m_update_map=false so the map is stable.
	QImage dummy(1, 1, QImage::Format_ARGB32_Premultiplied);
	QPainter qp(&dummy);
	QPen pen_;
	pen_.setWidthF(0.5);
	qp.setPen(pen_);
	qp.setFont(QETApp::diagramTextsFont(5));

	// PLC table is drawn by Element::drawPlcTable(), not by CrossRefItem
	if (m_element->elementData().m_master_type == ElementData::PLC)
		return;
	//Draw cross or contact, only if master element is linked.
	else if (! m_element->linkedElements().isEmpty())
	{
		m_update_map = true;
		XRefProperties::DisplayHas dh = m_properties.displayHas();

		if (dh == XRefProperties::Cross)
			drawAsCross(qp);
		else if (dh == XRefProperties::Contacts)
			drawAsContacts(qp);
		m_update_map = false;
	}

	autoPos();
	update();
	// Schedule a second update after the scene has finished laying out,
	// so the initial render uses the correct bounding rect.
	QTimer::singleShot(0, this, [this]{ update(); });
}

/**
	@brief CrossRefItem::autoPos
	Calculate and set position automatically.
*/
void CrossRefItem::autoPos()
{
	//We calculate the position according to the snapTo of the xrefproperties
	if (m_properties.snapTo() == XRefProperties::Bottom)
		QGIUtility::centerToBottomDiagram(this,
				      m_element,
				      m_properties.offset() <= 40
				      ? 5
				      : m_properties.offset());
	else
		QGIUtility::centerToParentBottom(this);
}

/**
	@brief CrossRefItem::sceneEvent
	@param event
	@return
*/
bool CrossRefItem::sceneEvent(QEvent *event)
{
	//By default when a QGraphicsItem is a child of a QGraphicsItemGroup
	//all events are forwarded to group.
	//We override it, when this Xref is in a group
	if(m_group)
	{
		switch (event->type())
		{
			case QEvent::GraphicsSceneHoverEnter:
				hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent *>(event));
				break;
			case QEvent::GraphicsSceneHoverMove:
				hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent *>(event));
				break;
			case QEvent::GraphicsSceneHoverLeave:
				hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent *>(event));
				break;
			case QEvent::GraphicsSceneMouseDoubleClick:
				mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event));
				break;
			default:break;
		}
		return true;
	}
	
	return QGraphicsObject::sceneEvent(event);
}

/**
	@brief CrossRefItem::paint
	Paint this item
	@param painter
	@param option
	@param widget
*/
void CrossRefItem::paint(
		QPainter *painter,
		const QStyleOptionGraphicsItem *option,
		QWidget *widget)
{
	Q_UNUSED(option)
	Q_UNUSED(widget)
	// Draw directly — no QPicture involved anywhere.
	// QPicture::play() + nested drawPicture() (m_hdr_no_ctc/m_hdr_nc_ctc)
	// caused a use-after-free crash (QRegion::begin, Qt5Gui+0x49af60)
	// confirmed by analysis of 19+ coredumps.
	// m_update_map=false: draw functions do not overwrite m_hovered_contacts_map.

	// PLC table is drawn by Element::drawPlcTable(), not by CrossRefItem
	if (m_element->elementData().m_master_type == ElementData::PLC)
		return;

	if (m_element->linkedElements().isEmpty()) return;

	QPen pen_;
	pen_.setWidthF(0.5);
	painter->save();
	painter->setPen(pen_);
	painter->setFont(QETApp::diagramTextsFont(5));

	m_update_map = false;
	XRefProperties::DisplayHas dh = m_properties.displayHas();
	if (dh == XRefProperties::Cross)
		drawAsCross(*painter);
	else if (dh == XRefProperties::Contacts)
		drawAsContacts(*painter);
	painter->restore();
}

/**
	@brief CrossRefItem::mouseDoubleClickEvent
	@param event
*/
void CrossRefItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
	event->accept();

	// Find the element under the click position directly from the map,
	// rather than relying on m_hovered_contact which may have been reset
	// by hoverMoveEvent between the two clicks of the double-click.
	QPointF pos = event->pos();
	Element *target = m_hovered_contact;

	if (!target) {
		for (auto it = m_hovered_contacts_map.begin();
		     it != m_hovered_contacts_map.end(); ++it) {
			if (it.value().contains(pos)) {
				target = it.key();
				break;
			}
		}
	}

	QetGraphicsItem::showItem(target);
}

/**
	@brief CrossRefItem::hoverEnterEvent
	@param event
*/
void CrossRefItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
	m_hovered_contact = nullptr;
		QGraphicsObject::hoverEnterEvent(event);
}

/**
	@brief CrossRefItem::hoverMoveEvent
	@param event
*/
void CrossRefItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
	QPointF pos = event->pos();

	if (m_hovered_contact)
	{
		foreach(QRectF rect, m_hovered_contacts_map.values(m_hovered_contact))
		{
			//Mouse hover the same rect than previous hover event
			if (rect.contains(pos))
			{
				QGraphicsObject::hoverMoveEvent(event);
				return;
			}
		}

		//At this point, mouse don't hover previous rect
		m_hovered_contact = nullptr;

		foreach (Element *elmt, m_hovered_contacts_map.keys())
		{
			foreach(QRectF rect, m_hovered_contacts_map.values(elmt))
			{
					//Mouse hover a contact
				if (rect.contains(pos))
				{
					m_hovered_contact = elmt;
				}
			}
		}

		updateLabel();
		QGraphicsObject::hoverMoveEvent(event);
		return;
	}
	else
	{
		foreach (Element *elmt, m_hovered_contacts_map.keys())
		{
			foreach(QRectF rect, m_hovered_contacts_map.values(elmt))
			{
				//Mouse hover a contact
				if (rect.contains(pos))
				{
					m_hovered_contact = elmt;
					updateLabel();
					QGraphicsObject::hoverMoveEvent(event);
					return;
				}
			}
		}
	}
}

/**
	@brief CrossRefItem::hoverLeaveEvent
	@param event
*/
void CrossRefItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
	m_hovered_contact = nullptr;
	updateLabel();
	QGraphicsObject::hoverLeaveEvent(event);
}

/**
	@brief CrossRefItem::linkedChanged
*/
void CrossRefItem::linkedChanged()
{
	for(const QMetaObject::Connection& c : m_slave_connection)
		disconnect(c);

	m_slave_connection.clear();

	if(!isVisible())
		return;
	
	for(Element *elmt : m_element->linkedElements())
	{
		m_slave_connection << connect(elmt,
					      &Element::xChanged,
					      this,
					      &CrossRefItem::updateLabel);
		m_slave_connection << connect(elmt,
					      &Element::yChanged,
					      this,
					      &CrossRefItem::updateLabel);
	}

	updateLabel();
}

/**
	@brief CrossRefItem::buildHeaderContact
	Draw NO and NC contact symbols directly onto painter.
	Previously used QPicture (m_hdr_no_ctc/m_hdr_nc_ctc) which caused
	use-after-free crashes via nested QPicture::play() calls.
*/
void CrossRefItem::buildHeaderContact(QPainter &painter, QPointF no_pos, QPointF nc_pos)
{
	QPen pen_;
	pen_.setWidthF(0.2);
	painter.save();
	painter.setPen(pen_);

	//draw the NO contact header symbol
	painter.drawLine(no_pos.x()+0, no_pos.y()+3, no_pos.x()+5, no_pos.y()+3);
	QPointF p1[3] = {
		QPointF(no_pos.x()+5,  no_pos.y()+0),
		QPointF(no_pos.x()+10, no_pos.y()+3),
		QPointF(no_pos.x()+15, no_pos.y()+3),
	};
	painter.drawPolyline(p1, 3);

	//draw the NC contact header symbol
	QPointF p2[3] = {
		QPointF(nc_pos.x()+0, nc_pos.y()+3),
		QPointF(nc_pos.x()+5, nc_pos.y()+3),
		QPointF(nc_pos.x()+5, nc_pos.y()+0)
	};
	painter.drawPolyline(p2, 3);
	QPointF p3[3] = {
		QPointF(nc_pos.x()+4,  nc_pos.y()+0),
		QPointF(nc_pos.x()+10, nc_pos.y()+3),
		QPointF(nc_pos.x()+15, nc_pos.y()+3),
	};
	painter.drawPolyline(p3, 3);

	painter.restore();
}

/**
	@brief CrossRefItem::setUpCrossBoundingRect
	Get the numbers of slaves elements linked to this parent element,
	for calculate the size of the cross bounding rect.
	The cross ref item is drawing according to the size of
	the cross bounding rect.
	@param painter
*/
void CrossRefItem::setUpCrossBoundingRect(QPainter &painter)
{
	//No need to calcul if nothing is linked
	if (m_element->isFree()) return;

	QStringList no_str, nc_str;

	// Helper lambda: build "[13-14] pos" string for an element.
	// For power contacts (e.g. 3-pole contactor), all named terminals
	// are collected (e.g. "[1-2-3-4-5-6] pos").
	// For other contacts (NO/NC/SW), only the first 2 named terminals
	// are used — users are expected to name and order their terminals
	// in the element editor.
	// Helper lambda: build "[13-14] pos" for an element.
	// - Power: all terminals sorted numerically → "[1-2-3-4-5-6] pos"
	// - SW with typed terminals: show relevant pair per column (handled below)
	// - Others: first 2 named terminals
	auto buildLabel = [this](Element *elmt, bool is_no_col) -> QString {
		const bool is_power =
			elmt->kindInformations()["type"].toString() == "power";
		const bool is_sw =
			elmt->kindInformations()["state"].toString() == "SW";

		QStringList tnames;

		if (is_sw) {
			// Check if terminals have explicit No/Nc/Common types
			bool has_typed = false;
			for (Terminal *t : elmt->terminals()) {
				if (!t) continue;
				if (t->terminalType() == TerminalData::No ||
					t->terminalType() == TerminalData::Nc ||
					t->terminalType() == TerminalData::Common) {
					has_typed = true; break;
				}
			}
			if (has_typed) {
				QString no_name, nc_name, common_name;
				for (Terminal *t : elmt->terminals()) {
					if (!t) continue;
					if (!t->name().isEmpty()) {
						if      (t->terminalType() == TerminalData::No)     no_name     = t->name();
						else if (t->terminalType() == TerminalData::Nc)     nc_name     = t->name();
						else if (t->terminalType() == TerminalData::Common) common_name = t->name();
					}
				}
				// NO column: show NO+Common pair; NC column: show NC+Common pair
				if (is_no_col)
					tnames << no_name << common_name;
				else
					tnames << nc_name << common_name;
			} else {
				// Fallback: first 2 named terminals
				for (Terminal *t : elmt->terminals()) {
					if (!t) continue;
					const QString tn = t->name();
					if (!tn.isEmpty()) { tnames << tn; if (tnames.size() >= 2) break; }
				}
			}
		} else {
			for (Terminal *t : elmt->terminals()) {
				if (!t) continue;
				const QString tn = t->name();
				if (!tn.isEmpty()) {
					tnames << tn;
					if (!is_power && tnames.size() >= 2) break;
				}
			}
			if (is_power) {
				std::sort(tnames.begin(), tnames.end(),
					[](const QString &a, const QString &b){
						int i_a = a.size();
						while (i_a > 0 && a[i_a-1].isDigit()) --i_a;
						int i_b = b.size();
						while (i_b > 0 && b[i_b-1].isDigit()) --i_b;
						bool a_ok = false, b_ok = false;
						int ai = a.mid(i_a).toInt(&a_ok);
						int bi = b.mid(i_b).toInt(&b_ok);
						if (a_ok && b_ok && a.left(i_a) == b.left(i_b))
							return ai < bi;
						return a < b;
					});
			}
		}

		QString pos = elementPositionText(elmt, true);
		if (!tnames.isEmpty() && m_properties.showTerminalName())
			return QStringLiteral("[") + tnames.join("-") + QStringLiteral("] ") + pos;
		return pos;
	};

	for (auto elmt : NOElements()) {
		no_str.append(buildLabel(elmt, true));
	}
	for (auto elmt : NCElements()) {
		nc_str.append(buildLabel(elmt, false));
	}

	//There is no string to display, we return now
	if (no_str.isEmpty() && nc_str.isEmpty()) return;

	//this is the default size of cross ref item
	QRectF default_bounding(0, 0, 40, header + cross_min_height);

	//Bounding rect of the NO text
	QRectF no_bounding;
	for (auto str : no_str)
	{
		QRectF bounding = painter.boundingRect(QRectF(0, 0, 500, 20), Qt::AlignLeft, str);
		no_bounding.setHeight(no_bounding.height() + bounding.height());
		if (bounding.width() > no_bounding.width())
			no_bounding.setWidth(bounding.width());
	}
	//Adjust according to the NO
	if (no_bounding.height() > default_bounding.height() - header)
		default_bounding.setHeight(no_bounding.height() + header); //adjust the height
	if (no_bounding.width() > default_bounding.width()/2)
		default_bounding.setWidth(no_bounding.width()*2);//adjust the width

	//Bounding rect of the NC text
	QRectF nc_bounding;
	for (auto str : nc_str)
	{
		QRectF bounding = painter.boundingRect(QRectF(0, 0, 500, 20), Qt::AlignLeft, str);
		nc_bounding.setHeight(nc_bounding.height() + bounding.height());
		if (bounding.width() > nc_bounding.width())
			nc_bounding.setWidth(bounding.width());
	}
	//Adjust according to the NC
	if (nc_bounding.height() > default_bounding.height() - header)
		default_bounding.setHeight(nc_bounding.height() + header); //adjust the height
	if (nc_bounding.width() > default_bounding.width()/2)
		default_bounding.setWidth(nc_bounding.width()*2);//adjust the width

	//Minor adjustement for better visual
	default_bounding.adjust(0, 0, 4, 0);
	m_shape_path.addRect(default_bounding);
	prepareGeometryChange();
	m_bounding_rect = default_bounding;
}

/**
	@brief CrossRefItem::drawAsCross
	Draw this crossref with a cross
	@param painter: painter to use
*/
void CrossRefItem::drawAsCross(QPainter &painter)
{
	//calculate the size of the cross
	setUpCrossBoundingRect(painter);
	m_drawed_contacts = 0;
	if (m_update_map) m_hovered_contacts_map.clear();

	//Bounding rect is empty that mean there's no contact to draw
	if (boundingRect().isEmpty()) return;

	//draw the cross
	QRectF br = boundingRect();
	painter.drawLine(br.width()/2, 0, br.width()/2, br.height());	//vertical line
	painter.drawLine(0, header, br.width(), header);	//horizontal line

	//Add the symbolic contacts (drawn directly, no QPicture)
	static const qreal hdr_symbol_width = 15.0;
	QPointF no_pos((m_bounding_rect.width()/4) - (hdr_symbol_width/2), 0);
	QPointF nc_pos((m_bounding_rect.width() * 3/4) - (hdr_symbol_width/2), 0);
	buildHeaderContact(painter, no_pos, nc_pos);

	//and fill it
	fillCrossRef(painter);
}

/**
	@brief CrossRefItem::drawAsContacts
	Draw this crossref with symbolic contacts
	@param painter painter to use
*/
void CrossRefItem::drawAsContacts(QPainter &painter)
{
	if (m_element -> isFree())
		return;

	m_drawed_contacts = 0;
	if (m_update_map) m_hovered_contacts_map.clear();
	QRectF bounding_rect;

	//Draw each linked contact
	foreach (Element *elmt,  m_element->linkedElements())
	{
		DiagramContext info = elmt->kindInformations();

		for (int i=0; i<info["number"].toInt(); i++)
		{
			int option = 0;

			QString state = info["state"].toString();
				 if (state == "NO") option = NO;
			else if (state == "NC") option = NC;
			else if (state == "SW") option = SW;
			else if (state == "Other") option = Other;

			QString type = info["type"].toString();
				 if (type == "power")    option += Power;
			else if (type == "delayOn")  option += DelayOn;
			else if (type == "delayOff") option += DelayOff;
			else if (type == "delayOnOff") option += DelayOnOff;

			QRectF br = drawContact(painter, option, elmt, i);
			bounding_rect = bounding_rect.united(br);
		}
	}

	bounding_rect.adjust(-30, -4, 4, 4);
	prepareGeometryChange();
	m_bounding_rect = bounding_rect;
	m_shape_path.addRect(bounding_rect);
}

/**
	@brief CrossRefItem::drawContact
	Draw one contact, the type of contact to draw is define in flags.
	@param painter : painter to use
	@param flags : define how to draw the contact (see enul CONTACTS)
	@param elmt : the element to display text (the position of the contact)
	@return The bounding rect of the draw (contact + text)
*/
QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, int pole_index)
{
	QString str = elementPositionText(elmt);

	// Collect terminal names from the element definition (.elmt)
	// e.g. name="13" and name="14" on each terminal
	// For power contacts, sort numerically and pick the pair for pole_index.
	// For SW contacts with typed terminals (No/Nc/Common), filter by role.
	QStringList terminal_names;
	const bool is_power_ctc =
		elmt->kindInformations()["type"].toString() == "power";
	const bool is_sw = (flags & SW) && !(flags & NOC);

	// Check if SW terminals have explicit No/Nc/Common types
	bool sw_has_typed_terminals = false;
	if (is_sw) {
		for (Terminal *t : elmt->terminals()) {
			if (!t) continue;
			if (t->terminalType() == TerminalData::No ||
				t->terminalType() == TerminalData::Nc ||
				t->terminalType() == TerminalData::Common) {
				sw_has_typed_terminals = true;
				break;
			}
		}
	}

	for (Terminal *t : elmt->terminals()) {
		if (!t) continue;
		const QString tname = t->name();
		if (!tname.isEmpty())
			terminal_names << tname;
	}

	if (is_power_ctc) {
		// Sort terminals alphanumerically so names like "R1","R2"... or "1","2"...
		// are ordered correctly. Extract trailing digits for numeric comparison;
		// fall back to full string comparison when no digits are found.
		std::sort(terminal_names.begin(), terminal_names.end(),
			[](const QString &a, const QString &b){
				// Extract trailing numeric part
				int i_a = a.size();
				while (i_a > 0 && a[i_a-1].isDigit()) --i_a;
				int i_b = b.size();
				while (i_b > 0 && b[i_b-1].isDigit()) --i_b;
				bool a_ok = false, b_ok = false;
				int ai = a.mid(i_a).toInt(&a_ok);
				int bi = b.mid(i_b).toInt(&b_ok);
				if (a_ok && b_ok && a.left(i_a) == b.left(i_b))
					return ai < bi;
				return a < b;
			});
		// Pick the pair for this pole: pole 0 → [0,1], pole 1 → [2,3], etc.
		int idx = pole_index * 2;
		if (idx + 1 < terminal_names.size())
			terminal_names = QStringList() << terminal_names[idx] << terminal_names[idx+1];
		else
			terminal_names.clear();
	} else if (is_sw && sw_has_typed_terminals) {
		// Build [NO_name, Common_name, NC_name] from typed terminals
		QString no_name, nc_name, common_name;
		for (Terminal *t : elmt->terminals()) {
			if (!t) continue;
			if (!t->name().isEmpty()) {
				if      (t->terminalType() == TerminalData::No)     no_name     = t->name();
				else if (t->terminalType() == TerminalData::Nc)     nc_name     = t->name();
				else if (t->terminalType() == TerminalData::Common) common_name = t->name();
			}
		}
		// drawText expects: [0]=NC, [1]=NO, [2]=Common
		// (drawText uses [1] for NO top-left, [0] for NC bottom-left, [2] for Common right)
		terminal_names.clear();
		terminal_names << nc_name << no_name << common_name;
	}

	int offset = m_drawed_contacts*10;
	QRectF bounding_rect = QRectF(0, offset, 24, 10);
	
	QPen pen = painter.pen();
	m_hovered_contact == elmt ? pen.setColor(Qt::blue) :pen.setColor(Qt::black);
	painter.setPen(pen);

	//Draw NO or NC contact
	if (flags &NOC)
	{
		bounding_rect = QRectF(0, offset, 24, 10);

		//draw the basic line
		painter.drawLine(0, offset+6, 8, offset+6);
		painter.drawLine(16, offset+6, 24, offset+6);

		// Draw terminal names on each side of the contact symbol
		// terminal_names[0] on the left, terminal_names[1] on the right
		if (!terminal_names.isEmpty() && m_properties.showTerminalName()) {
			painter.setFont(QETApp::diagramTextsFont(4));
			QRectF bt(0, offset, 24, 10);
			if (terminal_names.size() >= 1)
				painter.drawText(bt, Qt::AlignLeft|Qt::AlignTop, terminal_names[0]);
			if (terminal_names.size() >= 2)
				painter.drawText(bt, Qt::AlignRight|Qt::AlignTop, terminal_names[1]);
			painter.setFont(QETApp::diagramTextsFont(5));
		}

		//draw open contact
		if (flags &NO) {
			painter.drawLine(8, offset+9, 16, offset+6);
		}
		//draw close contact
		if (flags &NC) {
			QPointF p1[3] = {
				QPointF(8, offset+6),
				QPointF(9, offset+6),
				QPointF(9, offset+2.5)
			};
			painter.drawPolyline(p1,3);
			painter.drawLine(8, offset+3, 16, offset+6);
		}

		//draw half circle for power contact
		if (flags &Power) {
			QRectF arc(4, offset+4, 4, 4);
			if (flags &NO)
				painter.drawArc(arc, 180*16, 180*16);
			else
				painter.drawArc(arc, 0, 180*16);
		}

			// draw half circle for delay contact
		if(flags &Delay) {
				// for delay on contact
			if (flags &DelayOn) {
				if (flags &NO) {
					painter.drawLine(12, offset+8, 12, offset+11);
					QRectF r(9.5, offset+9, 5, 3);
					painter.drawArc(r, 180*16, 180*16);
				}
				if (flags &NC) {
					painter.drawLine(QPointF(12.5, offset+5), QPointF(12.5, offset+8));
					QRectF r(10, offset+6, 5, 3);
					painter.drawArc(r, 180*16, 180*16);
				}
			}
			// for delay off contact
			else if ( flags &DelayOff){
				if (flags &NO) {
					painter.drawLine(12, offset+8, 12, offset+9.5);
					QRectF r(9.5, offset+9.5, 5, 3);
					painter.drawArc(r, 0, 180*16);
				}
				if (flags &NC) {
					painter.drawLine(QPointF(12.5, offset+5), QPointF(12.5, offset+7));
					QRectF r(10, offset+7.5, 5, 3);
					painter.drawArc(r, 0, 180*16);
				}

			}
			else {
					// for delay on contact
				if (flags &NO) {
					painter.drawLine(12, offset+8, 12, offset+11);
					QRectF r(9.5, offset+11.7, 5, 3);
					painter.drawArc(r, 0, 180*16);
					QRectF rl(9.5, offset+9, 5, 3);
					painter.drawArc(rl, 180*16, 180*16);
				}
				if (flags &NC) {
					painter.drawLine(QPointF(12.5, offset+5), QPointF(12.5, offset+8));
					QRectF r(9.5, offset+10.7, 5, 3);
					painter.drawArc(r, 0, 180*16);
					QRectF rl(9.5, offset+8, 5, 3);
					painter.drawArc(rl, 180*16, 180*16);
				}
			}
		}

		QRectF text_rect = painter.boundingRect(QRectF(30, offset, 5, 10), Qt::AlignLeft | Qt::AlignVCenter, str);
		painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str);
		bounding_rect = bounding_rect.united(text_rect);

		if (m_update_map)
			m_hovered_contacts_map.insert(elmt, text_rect);

		++m_drawed_contacts;
	}

		//Draw a switch contact
	
	
	
	else if (flags &SW)
	{
		bounding_rect = QRectF(0, offset, 24, 20);
		bounding_rect.adjust(0, -4, 4, 4);

			//draw the NO side
		painter.drawLine(0, offset+6, 8, offset+6);
			//Draw the NC side
		QPointF p1[3] = {
			QPointF(0, offset+16),
			QPointF(8, offset+16),
			QPointF(8, offset+12)
		};
		painter.drawPolyline(p1, 3);

			//Draw the common side
		QPointF p2[3] = {
			QPointF(7, offset+14),
			QPointF(16, offset+11),
			QPointF(24, offset+11),
		};
		painter.drawPolyline(p2, 3);

		// Draw terminal names for switch contact (3 terminals)
		// terminal_names[0] = NC  (bottom-left)
		// terminal_names[1] = NO  (top-left)
		// terminal_names[2] = Common (right)
		if (!terminal_names.isEmpty() && m_properties.showTerminalName()) {
			painter.setFont(QETApp::diagramTextsFont(4));
			// Storage order set above: [0]=NC, [1]=NO, [2]=Common
			if (terminal_names.size() >= 2)
				painter.drawText(QRectF(0, offset, 8, 8),
						Qt::AlignLeft|Qt::AlignTop, terminal_names[1]);   // NO  top-left
			if (terminal_names.size() >= 3)
				painter.drawText(QRectF(16, offset+4, 8, 6),
						Qt::AlignRight|Qt::AlignTop, terminal_names[2]); // Common right
			if (terminal_names.size() >= 1)
				painter.drawText(QRectF(0, offset+9, 8, 6),
						Qt::AlignLeft|Qt::AlignTop, terminal_names[0]); // NC  bottom-left
			painter.setFont(QETApp::diagramTextsFont(5));
		}

			//Draw the half ellipse off delay
		if (flags &Delay)
		{
			painter.drawLine(12, offset+13, 12, offset+16);
			if (flags &DelayOn) {
				QRectF r(9.5, offset+14, 5, 3);
				painter.drawArc(r, 180*16, 180*16);
			}
			else if (flags &DelayOff) {
				QRectF r(9.5, offset+16.5, 5, 3);
				painter.drawArc(r, 0, 180*16);
			}
			else if (flags &DelayOnOff) {
				QRectF r(9.5, offset+14, 5, 3);
				painter.drawArc(r, 180*16, 180*16);
				QRectF rr(9.5, offset+17, 5, 3);
				painter.drawArc(rr, 0, 180*16);
			}
			
		}

			//Draw position text
		QRectF text_rect = painter.boundingRect(
					QRectF(30, offset+4, 5, 10),
					Qt::AlignLeft | Qt::AlignVCenter,
					str);
		painter.drawText(text_rect,
				 Qt::AlignLeft | Qt::AlignVCenter,
				 str);
		bounding_rect = bounding_rect.united(text_rect);

		if (m_update_map)
			m_hovered_contacts_map.insert(elmt, text_rect);

			//a switch contact take place of two normal contact
		m_drawed_contacts += 2;
		
	}
	
		//Draw Other symbol "ↈ"
	else if(flags &Other)
	{
		bounding_rect = QRectF(0, offset, 24, 20);
		bounding_rect.adjust(0, -4, 4, 4);
	
			//Draw the first arc symbol
		QRectF r(8, offset+4, 5, 3);
				painter.drawArc(r, 10*16, 270*16);
		
			//Draw the second arc symbol
		QRectF r2(11.2, offset+4, 5, 3);
				painter.drawArc(r2, 160*16, 300*16);

			//Draw position text
		QRectF text_rect = painter.boundingRect(
					QRectF(30, offset, 5, 10), 
					Qt::AlignLeft | Qt::AlignVCenter, 
					str);
		painter.drawText(text_rect,
					Qt::AlignLeft | Qt::AlignVCenter, 
					str);
		bounding_rect = bounding_rect.united(text_rect);

		if (m_update_map)
			m_hovered_contacts_map.insert(elmt, text_rect);
		++m_drawed_contacts;
	}
		return bounding_rect;
}


/**
	@brief CrossRefItem::fillCrossRef
	Fill the content of the cross ref
	@param painter painter to use.
*/
void CrossRefItem::fillCrossRef(QPainter &painter)
{
	if (m_element->isFree()) return;

	qreal middle_cross = m_bounding_rect.width()/2;

		//Fill NO
	QPointF no_top_left(0, header);
	foreach(Element *elmt, NOElements())
	{
		QPen pen = painter.pen();
		m_hovered_contact == elmt ? pen.setColor(Qt::blue) :pen.setColor(Qt::black);
		painter.setPen(pen);

		// Collect terminal names for NO column.
		// Power: all terminals sorted numerically.
		// SW with typed terminals: NO+Common pair.
		// Others: first 2 named terminals.
		const bool is_power_no =
			elmt->kindInformations()["type"].toString() == "power";
		const bool is_sw_no =
			elmt->kindInformations()["state"].toString() == "SW";
		QStringList tnames;
		if (is_sw_no) {
			bool has_typed = false;
			for (Terminal *t : elmt->terminals()) {
				if (!t) continue;
				if (t->terminalType() == TerminalData::No ||
					t->terminalType() == TerminalData::Nc ||
					t->terminalType() == TerminalData::Common) {
					has_typed = true; break;
				}
			}
			if (has_typed) {
				QString no_name, common_name;
				for (Terminal *t : elmt->terminals()) {
					if (!t) continue;
					if (!t->name().isEmpty()) {
						if      (t->terminalType() == TerminalData::No)     no_name     = t->name();
						else if (t->terminalType() == TerminalData::Common) common_name = t->name();
					}
				}
				tnames << no_name << common_name;
			} else {
				for (Terminal *t : elmt->terminals()) {
					if (!t) continue;
					const QString tn = t->name();
					if (!tn.isEmpty()) { tnames << tn; if (tnames.size() >= 2) break; }
				}
			}
		} else {
			for (Terminal *t : elmt->terminals()) {
				if (!t) continue;
				const QString tn = t->name();
				if (!tn.isEmpty()) {
					tnames << tn;
					if (!is_power_no && tnames.size() >= 2) break;
				}
			}
		}
		QString terminal_label;
		if (!tnames.isEmpty() && m_properties.showTerminalName())
			terminal_label = QStringLiteral("[") + tnames.join("-") + QStringLiteral("]");

		QString str = elementPositionText(elmt, true);
		if (!terminal_label.isEmpty())
			str = terminal_label + QStringLiteral(" ") + str;

		QRectF bounding = painter.boundingRect(
					QRectF(no_top_left,
					       QSize(middle_cross, 1)),
					Qt::AlignLeft,
					str);
		painter.drawText(bounding, Qt::AlignLeft, str);

		if (m_update_map) {
			QString pos_str = elementPositionText(elmt, true);
			QRectF pos_rect = painter.boundingRect(bounding, Qt::AlignRight, pos_str);
			pos_rect.adjust(-2, -1, 2, 1); // extend hit area slightly
			m_hovered_contacts_map.insert(elmt, pos_rect);
		}

		no_top_left.ry() += bounding.height();
	}

		//Fill NC
	QPointF nc_top_left(middle_cross, header);
	foreach(Element *elmt, NCElements())
	{
		QPen pen = painter.pen();
		m_hovered_contact == elmt ? pen.setColor(Qt::blue)
					  :pen.setColor(Qt::black);
		painter.setPen(pen);

		// Collect terminal names for NC column.
		// Power: all terminals sorted numerically.
		// SW with typed terminals: NC+Common pair.
		// Others: first 2 named terminals.
		const bool is_power_nc =
			elmt->kindInformations()["type"].toString() == "power";
		const bool is_sw_nc =
			elmt->kindInformations()["state"].toString() == "SW";
		QStringList tnames_nc;
		if (is_sw_nc) {
			bool has_typed = false;
			for (Terminal *t : elmt->terminals()) {
				if (!t) continue;
				if (t->terminalType() == TerminalData::No ||
					t->terminalType() == TerminalData::Nc ||
					t->terminalType() == TerminalData::Common) {
					has_typed = true; break;
				}
			}
			if (has_typed) {
				QString nc_name, common_name;
				for (Terminal *t : elmt->terminals()) {
					if (!t) continue;
					if (!t->name().isEmpty()) {
						if      (t->terminalType() == TerminalData::Nc)     nc_name     = t->name();
						else if (t->terminalType() == TerminalData::Common) common_name = t->name();
					}
				}
				tnames_nc << nc_name << common_name;
			} else {
				for (Terminal *t : elmt->terminals()) {
					if (!t) continue;
					const QString tn = t->name();
					if (!tn.isEmpty()) { tnames_nc << tn; if (tnames_nc.size() >= 2) break; }
				}
			}
		} else {
			for (Terminal *t : elmt->terminals()) {
				if (!t) continue;
				const QString tn = t->name();
				if (!tn.isEmpty()) {
					tnames_nc << tn;
					if (!is_power_nc && tnames_nc.size() >= 2) break;
				}
			}
		}
		QString terminal_label;
		if (!tnames_nc.isEmpty() && m_properties.showTerminalName())
			terminal_label = QStringLiteral("[") + tnames_nc.join("-") + QStringLiteral("]");

		QString str = elementPositionText(elmt, true);
		if (!terminal_label.isEmpty())
			str = terminal_label + QStringLiteral(" ") + str;

		QRectF bounding = painter.boundingRect(
					QRectF(nc_top_left,
					       QSize(middle_cross, 1)),
					Qt::AlignRight,
					str);
		painter.drawText(bounding, Qt::AlignRight, str);

		if (m_update_map) {
			QString pos_str = elementPositionText(elmt, true);
			QRectF pos_rect = painter.boundingRect(bounding, Qt::AlignRight, pos_str);
			pos_rect.adjust(-2, -1, 2, 1); // extend hit area slightly
			m_hovered_contacts_map.insert(elmt, pos_rect);
		}

		nc_top_left.ry() += bounding.height();
	}
}

/**
	@brief CrossRefItem::AddExtraInfo
	Add the comment info of the parent item if needed.
	@param painter painter to use for draw the text
	@param type type of Info do be draw e.g. comment, location.
*/
void CrossRefItem::AddExtraInfo(QPainter &painter, const QString& type)
{
	QString text = autonum::AssignVariables::formulaToLabel(
				m_element->elementInformations()[type].toString(),
				m_element->rSequenceStruct(),
				m_element->diagram(),
				m_element);
	bool must_show  = m_element -> elementInformations().keyMustShow(type);

	if (!text.isEmpty() && must_show)
	{
		painter.save();
		painter.setFont(QETApp::diagramTextsFont(6));

		QRectF r, text_bounding;
		qreal center = boundingRect().center().x();
		qreal width = boundingRect().width() > 70
				? boundingRect().width()/2
				: 35;

		r = QRectF(QPointF(center - width,
				   boundingRect().bottom()),
				   QPointF(center + width,
					   boundingRect().bottom() + 1));

		text_bounding = painter.boundingRect(
					r,
					Qt::TextWordWrap | Qt::AlignHCenter,
					text);
		painter.drawText(text_bounding,
				 Qt::TextWordWrap | Qt::AlignHCenter,
				 text);

		text_bounding.adjust(-1,0,1,0); //adjust only for better visual

		m_shape_path.addRect(text_bounding);
		prepareGeometryChange();
		m_bounding_rect = m_bounding_rect.united(text_bounding);
		if (type == "comment") painter.drawRoundedRect(text_bounding,
							       2,
							       2);
		painter.restore();
	}
}

/**
	@brief CrossRefItem::NOElements
	@return The linked elements of m_element which are open or switch contact.
	If linked element is a power contact,
	xref property is set to not show power contact
	and this cross item must be drawn as a cross,
	the element is not appended in the list.
*/
QList<Element *> CrossRefItem::NOElements() const
{
	QList<Element *> no_list;

	foreach (Element *elmt, m_element->linkedElements())
	{
		//We continue if element is a power contact and xref property
		//is set to not show power contact
		if (m_properties.displayHas() == XRefProperties::Cross &&
			!m_properties.showPowerContact() &&
			elmt -> kindInformations()["type"].toString() == "power")
			continue;

		QString state = elmt->kindInformations()["state"].toString();

		if (state == "NO" || state == "SW")
		{
			no_list.append(elmt);
		}
	}

	return no_list;
}

/**
	@brief CrossRefItem::NCElements
	@return The linked elements of m_element which are close
	or switch contact
	If linked element is a power contact,
	xref property is set to not show power contact
	and this cross item must be drawn as a cross,
	the element is not appended in the list.
*/
QList<Element *> CrossRefItem::NCElements() const
{
	QList<Element *> nc_list;

	foreach (Element *elmt, m_element->linkedElements())
	{
		//We continue if element is a power contact and xref property
		//is set to not show power contact
		if (m_properties.displayHas() == XRefProperties::Cross &&
			!m_properties.showPowerContact() &&
			elmt -> kindInformations()["type"].toString() == "power")
			continue;

		QString state = elmt->kindInformations()["state"].toString();

		if (state == "NC" || state == "SW")
		{
			nc_list.append(elmt);
		}
	}

	return nc_list;
}

/**
 * @brief CrossRefItem::drawAsPlcTable
 * Draw the PLC IO table for PLC master elements.
 * The table shows columns: Type | Address | Function | Comment | CrossRef
 * Columns and rows are configurable via PlcMasterData.
 * @param painter painter to use
 */
void CrossRefItem::drawAsPlcTable(QPainter &painter)
{
	// Get PLC data from the element
	ElementData ed = m_element->elementData();
	if (ed.m_master_type != ElementData::PLC)
		return;

	ElementData::PlcMasterData plc_data = ed.plcMasterData();
	if (plc_data.ios.isEmpty())
		return;

	// Define column indices
	const int COL_TYPE      = 0;
	const int COL_ADDRESS   = 1;
	const int COL_FUNCTION  = 2;
	const int COL_COMMENT   = 3;
	const int COL_CROSSREF  = 4;
	const int COL_COUNT     = 5;

	// Column headers (French)
	QMap<int, QString> headers;
	headers[COL_TYPE]     = QObject::tr("Type");
	headers[COL_ADDRESS]  = QObject::tr("Adresse");
	headers[COL_FUNCTION] = QObject::tr("Fonction");
	headers[COL_COMMENT]  = QObject::tr("Commentaire");
	headers[COL_CROSSREF] = QObject::tr("Réf. croisée");

	// Build list of visible columns
	QList<int> visible_cols;
	for (int i = 0; i < COL_COUNT; ++i) {
		if (plc_data.colVisible.value(i, true))
			visible_cols.append(i);
	}
	if (visible_cols.isEmpty())
		return;

	// Default column widths if not set (in scene units, roughly mm * 2.835)
	QMap<int, qreal> col_widths;
	for (int col : visible_cols) {
		if (plc_data.colWidths.contains(col) && plc_data.colWidths[col] > 0)
			col_widths[col] = plc_data.colWidths[col];
		else {
			switch (col) {
				case COL_TYPE:     col_widths[col] = 35; break;
				case COL_ADDRESS:  col_widths[col] = 25; break;
				case COL_FUNCTION: col_widths[col] = 50; break;
				case COL_COMMENT:  col_widths[col] = 40; break;
				case COL_CROSSREF: col_widths[col] = 30; break;
				default:           col_widths[col] = 30; break;
			}
		}
	}

	qreal row_h = plc_data.rowHeight > 0 ? plc_data.rowHeight : 8.0;
	qreal header_h = row_h + 2.0;

	// Calculate total width
	qreal total_width = 0;
	for (int col : visible_cols)
		total_width += col_widths[col];

	// Calculate total height: header + all IO rows
	int total_ios = plc_data.ios.size();

	// Collect active break positions (sorted)
	QList<int> breaks;
	for (int bp : plc_data.breakPositions) {
		if (bp > 0 && bp < total_ios && !breaks.contains(bp))
			breaks.append(bp);
	}
	std::sort(breaks.begin(), breaks.end());

	// Build block boundaries
	QList<int> block_starts;
	block_starts.append(0);
	for (int bp : breaks)
		block_starts.append(bp);

	qreal total_height;
	int block_count = block_starts.size();

	if (block_count > 1) {
		int max_rows = 0;
		for (int b = 0; b < block_count; ++b) {
			int start = block_starts.at(b);
			int end = (b + 1 < block_starts.size()) ? block_starts.at(b + 1) : total_ios;
			max_rows = qMax(max_rows, end - start);
		}
		total_height = header_h + max_rows * row_h;
		total_width = total_width * block_count + (block_count - 1) * 3;
	} else {
		total_height = header_h + total_ios * row_h;
	}

	// Draw background rectangle
	QRectF bg_rect(0, 0, total_width, total_height);
	painter.fillRect(bg_rect, Qt::white);
	QPen border_pen(Qt::black, 0.5);
	painter.setPen(border_pen);
	painter.drawRect(bg_rect);

	// Draw header row
	qreal x_offset = 0;

	for (int block = 0; block < block_count; ++block) {
		qreal block_x = block * (col_widths.value(visible_cols.first(), 30) * visible_cols.size() + 3);
		qreal cx = block_x;

		// Draw column headers
		QFont header_font = painter.font();
		header_font.setBold(true);
		painter.setFont(header_font);

		for (int col : visible_cols) {
			QRectF header_rect(cx, 0, col_widths[col], header_h);
			painter.fillRect(header_rect, QColor(220, 220, 220));
			painter.drawRect(header_rect);
			painter.drawText(header_rect, Qt::AlignCenter, headers[col]);
			cx += col_widths[col];
		}

		painter.setFont(painter.font());

		// Draw IO rows for this block
		int start_idx = block_starts.at(block);
		int end_idx = (block + 1 < block_starts.size()) ? block_starts.at(block + 1) : total_ios;

		for (int row = 0; row < (end_idx - start_idx); ++row) {
			int io_idx = start_idx + row;
			const ElementData::PlcIO &io = plc_data.ios.at(io_idx);

			qreal ry = header_h + row * row_h;
			cx = block_x;

			// Get linked slave element for this IO (via group index)
			QString cross_ref_text = io.crossRef;
			for (Element *slave : m_element->linkedElements()) {
				if (m_element->groupIndexForElement(slave) == io_idx) {
					cross_ref_text = elementPositionText(slave);
					break;
				}
			}

			for (int col : visible_cols) {
				QRectF cell_rect(cx, ry, col_widths[col], row_h);
				painter.drawRect(cell_rect);

				QString cell_text;
				switch (col) {
					case COL_TYPE:
						cell_text = ElementData::translatedPlcIOType(io.type);
						break;
					case COL_ADDRESS:
						cell_text = io.address;
						break;
					case COL_FUNCTION:
						cell_text = io.functionText;
						break;
					case COL_COMMENT:
						cell_text = io.comment;
						break;
					case COL_CROSSREF:
						cell_text = cross_ref_text;
						break;
				}

				QRectF text_rect = cell_rect.adjusted(1, 0, -1, 0);
				painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);

				// If function text doesn't fit, try to show as much as possible
				if (col == COL_FUNCTION && !io.functionText.isEmpty()) {
					QFontMetrics fm(painter.font());
					QString elided = fm.elidedText(io.functionText, Qt::ElideRight, text_rect.width());
					// Clear and redraw with elided text
					painter.save();
					painter.setBrush(Qt::white);
					painter.drawRect(text_rect);
					painter.restore();
					painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, elided);
				}

				// Store hover map for cross-reference cells
				if (col == COL_CROSSREF && m_update_map && !cross_ref_text.isEmpty()) {
					for (Element *slave : m_element->linkedElements()) {
						if (m_element->groupIndexForElement(slave) == io_idx) {
							m_hovered_contacts_map.insert(slave, cell_rect);
							break;
						}
					}
				}

				cx += col_widths[col];
			}
		}
	}

	prepareGeometryChange();
	m_bounding_rect = bg_rect;
	m_shape_path.addRect(bg_rect);
}
