// Copyright (c) 2012, STANISLAW ADASZEWSKI
// SPDX-License-Identifier: BSD-3-Clause
//
// Portions Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Originally derived from Stanislaw Adaszewski's Qt Node Editor (qneblock); since
// re-authored for wiRedPanda. The BSD attribution above is retained as a license
// obligation for the derived design.

/** \file
 * \brief Port classes: Port (base), InputPort, and OutputPort.
 */

#pragma once

#include <QBrush>
#include <QGraphicsPathItem>
#include <QPen>

#include "App/Core/Enums.h"

class GraphicElement;
class Connection;
class Port;

/**
 * \class Port
 * \brief Abstract base class for circuit element ports (connection endpoints).
 *
 * \details A port is the small glyph drawn at the edge of a graphic element where
 * wires attach — a circle for inputs, a right-pointing triangle for outputs.
 * It stores a reference to the owning GraphicElement, all attached
 * Connections, and its current logical status.
 *
 * Concrete subclasses InputPort and OutputPort differentiate direction.
 */
class Port : public QGraphicsPathItem
{
public:
    // --- Type ---

    enum { Type = QGraphicsItem::UserType + 1 };
    int type() const override { return Type; }

    /// \reimp Hit-testing uses the full ±kRadius square regardless of the painted glyph.
    QPainterPath shape() const override;

    /// \reimp Pads the default pen-exact bound by a small margin so DeviceCoordinateCache
    /// has room to antialias the stroke edge instead of clipping it (visible at high zoom).
    QRectF boundingRect() const override;

    /// \reimp Draws with m_currentPen instead of the item's own pen() -- see updateTheme()
    /// for why -- otherwise identical to the default QGraphicsPathItem paint.
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    // --- Lifecycle ---

    /// Constructs the port with optional \a parent item.
    explicit Port(QGraphicsItem *parent = nullptr);
    virtual ~Port() = default;

    // --- Element Access ---

    /// Returns the graphic element that owns this port.
    GraphicElement *graphicElement() { return m_graphicElement; }
    /// \overload
    const GraphicElement *graphicElement() const { return m_graphicElement; }

    // --- Identity & Status ---

    /// Returns the port's label text.
    QString name() const;

    /// Returns the default status applied when the port is unconnected.
    Status defaultValue() const { return m_defaultStatus; }

    /// Returns the current logical status (Active/Inactive/Unknown/Error).
    Status status() const { return m_status; }

    /**
     * \brief Returns \c true if this port is connected to \a otherPort via any wire.
     * \param otherPort Port to check for an existing connection.
     */
    bool isConnected(Port *otherPort);

    /// Returns \c true if a connection to this port is mandatory.
    bool isRequired() const;

    /// Returns the list of wires attached to this port.
    const QList<Connection *> &connections() const;

    /// Returns the port's visual/logical index within the element.
    int index() const;

    /// Returns this port's index within the element's combined input+output port
    /// sequence: index() for input ports, index() + graphicElement()->inputSize()
    /// for output ports. The single source of truth for the port-ordering half of
    /// the serial-ID formula.
    int globalIndex() const;

    /// Packs \a elementBase and \a globalIndex into a serial ID: (elementBase << 16)
    /// | (globalIndex & 0xFFFF). The single source of truth for the serial-ID format
    /// used to resolve connection endpoints across save/load.
    static quint64 makeSerialId(quint64 elementBase, int globalIndex);

    /// Returns \c true if this is an input port. Pure virtual.
    virtual bool isInput() const = 0;

    /// Returns \c true if this is an output port. Pure virtual.
    virtual bool isOutput() const = 0;

    /// Returns \c true if the port satisfies its connection requirement. Pure virtual.
    virtual bool isValid() const = 0;

    // --- Status Management ---

    virtual void setStatus(Status status) = 0;

    // --- Connection Management ---

    /// Registers \a conn as a connection attached to this port.
    void attachConnection(Connection *conn);

    /**
     * \brief Removes \a conn from this port's connection list.
     * \param conn Wire to detach.
     */
    void detachConnection(Connection *conn);

    /// Applies hover-enter visual feedback.
    void hoverEnter();

    /// Reverts hover-enter visual feedback.
    void hoverLeave();

    /// Reapplies the status-based pen and brush from the current ThemeManager palette.
    void updateTheme();

    /**
     * \brief Sets the brush used to fill the port shape.
     * \param currentBrush New brush.
     */
    void setCurrentBrush(const QBrush &currentBrush);

    /// Returns the pen paint() draws the port outline with for its current status.
    QPen currentPen() const { return m_currentPen; }

    /**
     * \brief Sets the status applied when the port has no connection.
     * \param defaultStatus Default status value.
     */
    void setDefaultStatus(const Status defaultStatus);

    /**
     * \brief Binds this port to \a graphicElement.
     * \param graphicElement Owning graphic element.
     */
    void setGraphicElement(GraphicElement *graphicElement);

    /**
     * \brief Sets the port's visual index within the element.
     * \param index New index.
     */
    void setIndex(const int index);

    /**
     * \brief Sets the label text shown next to the port.
     * \param name Label string (e.g. "A", "CLK").
     */
    void setName(const QString &name);

    /**
     * \brief Marks whether a wire to this port is mandatory.
     * \param required \c true if a connection is required for valid simulation.
     * \note Does not affect the port's default status — call setDefaultStatus() separately.
     */
    void setRequired(const bool required);

    /// Triggers a path update on all attached connections.
    void updateConnections();

protected:
    // --- Qt Event Handling ---

    /// \reimp
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;

    /**
     * \brief Drains all attached connections, breaking back-references before deletion.
     * \param isInput True if this is an input port (sets endPort to nullptr); false for output.
     * \details Called from the InputPort and OutputPort destructors to avoid
     * re-entrant disconnect() calls into a partially destroyed port.
     */
    void drainConnections(bool isInput);

    /// Hit-area / glyph half-size in pixels. ±kRadius gives a 10×10 px clickable square,
    /// large enough to hit reliably without obscuring nearby elements.
    static constexpr int kRadius = 5;

    // --- Members ---

    GraphicElement *m_graphicElement = nullptr;
    QBrush m_currentBrush;
    /// Pen paint() actually draws the outline with, kept separate from the item's own
    /// QGraphicsPathItem pen -- see updateTheme() for why (avoids an unneeded BSP-tree
    /// re-index on every status colour change; all four status pens share the same width,
    /// so unlike Connection this never needs to fall back to the item's real pen).
    QPen m_currentPen;
    QList<Connection *> m_connections;
    QString m_name;
    Status m_defaultStatus = Status::Unknown;
    Status m_status = Status::Unknown;
    bool m_required = true;
    int m_index = 0;

private:
    QBrush currentBrush() const;
};

/**
 * \class InputPort
 * \brief A port that receives a signal (the destination end of a wire).
 *
 * \details Input ports are valid when they have exactly one connected wire or
 * when they are optional (isRequired() == false).
 */
class InputPort : public Port
{
public:
    /// Constructs an input port attached to \a parent.
    explicit InputPort(QGraphicsItem *parent = nullptr);
    ~InputPort() override;

    // --- Type Queries ---

    /// \reimp
    bool isInput() const override;
    /// \reimp
    bool isOutput() const override;
    /// \reimp
    bool isValid() const override;

    // --- Status Management ---

    /// \reimp
    void setStatus(const Status status) override;

private:
    Q_DISABLE_COPY_MOVE(InputPort)
};

/**
 * \class OutputPort
 * \brief A port that drives a signal (the source end of a wire).
 *
 * \details Output ports can fan out to multiple input ports.
 * They are always considered valid regardless of connection count.
 */
class OutputPort : public Port
{
public:
    /// Constructs an output port attached to \a parent.
    explicit OutputPort(QGraphicsItem *parent = nullptr);
    ~OutputPort() override;

    // --- Type Queries ---

    /// \reimp
    bool isInput() const override;
    /// \reimp
    bool isOutput() const override;
    /// \reimp
    bool isValid() const override;

    // --- Status Management ---

    /// \reimp
    void setStatus(const Status status) override;

private:
    Q_DISABLE_COPY_MOVE(OutputPort)
};
