// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
// SPDX-License-Identifier: GPL-3.0-or-later

/** \file
 * \brief Circuit and waveform file serialization/deserialization utilities.
 */

#pragma once

#include <QCoreApplication>
#include <QFileInfo>
#include <QKeySequence>
#include <QMap>
#include <QSet>
#include <QString>
#include <QVersionNumber>

#include "App/IO/SerializationContext.h"

class QGraphicsItem;
class Port;

/**
 * \class Serialization
 * \brief Static utility class for reading and writing circuit and waveform files.
 *
 * \details All I/O passes through a QDataStream; the format is versioned using a
 * magic header followed by the version number.  The context object carries per-load
 * state (port map, version, IC directory, optional copy-operation) so that element
 * implementations receive everything they need without global variables.
 */
class Serialization
{
    Q_DECLARE_TR_FUNCTIONS(Serialization)

public:
    /**
     * \brief Serializes \a items to \a stream in the current .panda binary format.
     * \param items Items to serialize (graphic elements and connections).
     * \param stream Destination data stream.
     * \param options No default -- every caller must state whether this builds a
     *        genuine .panda file or an in-session snapshot; see SerializationPurpose.
     */
    static void serialize(const QList<QGraphicsItem *> &items, QDataStream &stream, SerializationOptions options);

    /**
     * \brief Deserializes items from \a stream until the stream is exhausted.
     * \param stream Source data stream.
     * \param context Per-load context (portMap, version, contextDir).
     * \return List of deserialized QGraphicsItems ready to be added to a scene.
     */
    static QList<QGraphicsItem *> deserialize(QDataStream &stream, SerializationContext &context);

    // --- View Serialization ---

    /// Returns the canvas viewport rectangle from the last saved session.
    static QRectF loadRect(QDataStream &stream, const QVersionNumber &version);

    // --- Waveform Serialization ---

    /// Returns the BeWavedDolphin waveform file name stored in \a stream at \a version.
    static QString loadDolphinFileName(QDataStream &stream, const QVersionNumber &version);

    // --- Utility ---

    /// Returns the human-readable element type name for the given \a type integer.
    static QString typeName(const int type);

    // --- File Headers ---

    /// Reads and validates the .panda circuit file header; returns the stored version number.
    static QVersionNumber readPandaHeader(QDataStream &stream);

    /**
     * \brief Reads the file-level metadata \c QMap<QString,QVariant> from \a stream
     *        without calling \c QList::reserve() with a stream-controlled count.
     *
     * \details Qt's built-in \c operator>> for \c QList (used internally by
     * \c QStringList and any other list-valued \c QVariant) calls \c reserve(n)
     * before reading a single element, so a fuzz-controlled \c n causes an
     * immediate multi-GB allocation.  This function intercepts the \c QVariant
     * type tag and dispatches to bounded readers for each supported type
     * (\c QString, \c QStringList, \c QByteArray, and scalar types); unknown
     * types throw \c PANDACEPTION so the caller rejects the file cleanly.
     */
    static QMap<QString, QVariant> readBoundedMetadata(QDataStream &stream);

    /**
     * \brief Reads a QString from \a stream, refusing to allocate more bytes than
     *        remain in the stream (prevents OOM on fuzz-controlled length fields).
     */
    static QString readBoundedString(QDataStream &stream);

    /**
     * \brief Reads a QKeySequence from \a stream, rejecting an implausible/oversized
     *        internal key-combination count before QDataStream's default QKeySequence
     *        deserialization would reserve() it (a QKeySequence holds at most 4 key
     *        combinations, so a larger count can only be a corrupt/crafted stream).
     */
    static QKeySequence readBoundedKeySequence(QDataStream &stream);

    /**
     * \brief Reads a \c QMap<QString,QByteArray> from \a stream with bounds checking.
     *
     * Used by the clipboard paste path to safely deserialise the IC blob registry
     * without allowing a fuzz-controlled entry count to cause OOM via
     * \c QDataStream::operator>>(QMap<QString,QByteArray>&).
     */
    static QMap<QString, QByteArray> readBoundedBlobMap(QDataStream &stream);

    /**
     * \brief Reads and validates the BeWavedDolphin waveform file header.
     * \param stream Source data stream positioned at the start of the file.
     */
    static void readDolphinHeader(QDataStream &stream);

    /**
     * \brief Writes the BeWavedDolphin waveform file header to \a stream.
     * \param stream Destination data stream.
     */
    static void writeDolphinHeader(QDataStream &stream);

    /**
     * \brief Writes the .panda circuit file header to \a stream.
     * \param stream Destination data stream.
     */
    static void writePandaHeader(QDataStream &stream);

    /**
     * \brief Compresses \a payload (qCompress) and writes it to \a stream.
     *
     * \details Used by every \c SerializationPurpose::PortableFile writer, right
     * after the metadata + elements + connections for a .panda file (or an
     * embedded-IC blob) have been serialized into an in-memory buffer. Paired
     * with \c readPayload(). Never used for \c SerializationPurpose::InMemorySnapshot
     * writers (undo stack, clipboard, drag-and-drop) — those keep writing their
     * payload directly, uncompressed, exactly as before.
     */
    static void writePayload(QDataStream &stream, const QByteArray &payload);

    /**
     * \brief Reads the remainder of \a stream's device, decompressing it first
     *        if \a version indicates the compressed-payload format (Rev100+).
     *
     * \details Bounds-checks \c qCompress()'s 4-byte big-endian uncompressed-size
     * header before calling \c qUncompress(), which otherwise allocates that many
     * bytes unconditionally -- a classic decompression-bomb vector for a crafted
     * file claiming an implausible size. Throws \c PANDACEPTION on an implausible
     * size or on decompression failure (\c qUncompress() returning a null
     * \c QByteArray). Returns the raw bytes unchanged for pre-Rev100 files.
     */
    static QByteArray readPayload(QDataStream &stream, const QVersionNumber &version);

    /**
     * \brief Copies \a fileName to a versioned sidecar before overwriting it during migration.
     * \param fileName Absolute path of the file to back up.
     * \param version  The old file-format version (used to build the sidecar name).
     *
     * \details The backup name has the form \c basename.vX.Y.Z.ext (e.g. \c ic.v4.1.panda).
     * If a backup with that name already exists it is left untouched.
     * Silently logs a warning if the copy fails; does not throw.
     */
    static void createVersionedBackup(const QString &fileName, const QVersionNumber &version);

    // --- Panda Preamble ---

    /// Result of reading a .panda file preamble (header + dolphin + rect + metadata).
    struct Preamble {
        QVersionNumber version;
        QMap<QString, QVariant> metadata;
        /// Bytes remaining after metadata was parsed (elements + connections),
        /// already decompressed if the file uses the compressed-payload format.
        /// Callers that continue parsing elements construct their own QDataStream
        /// over this instead of continuing to read from the stream passed to
        /// readPreamble() -- that stream's device is fully consumed by the time
        /// readPreamble() returns, since decompression required buffering the
        /// whole payload up front.
        QByteArray remainingPayload;
    };

    /// Reads the full .panda preamble: header, dolphin filename, rect, and metadata (V_4_5+).
    static Preamble readPreamble(QDataStream &stream);

    // --- Embedded ICs ---

    /**
     * \brief Extracts the embedded IC registry from a metadata map.
     *
     * \param metadata   The file-level metadata map.
     * \param fileVersion The version of the .panda file the metadata was read from.
     *                   Used to select the correct QDataStream encoding for the blob:
     *                   files < V_5_0 used an unversioned stream (Qt default at build time);
     *                   files >= V_5_0 use an explicit Qt_5_12-versioned stream.
     *
     * Returns an empty map if no blob registry key is present.
     */
    static QMap<QString, QByteArray> deserializeBlobRegistry(const QMap<QString, QVariant> &metadata,
                                                              const QVersionNumber &fileVersion);

    /// Serializes embedded ICs into a metadata map (sets the "embeddedICs" key).
    static void serializeBlobRegistry(const QMap<QString, QByteArray> &blobs, QMap<QString, QVariant> &metadata);

    // --- File Utilities ---

    /**
     * \brief Copies a .panda file and its file-backed IC dependencies to \a destPath.
     * \param srcPath  Source .panda file info.
     * \param destPath Destination .panda file info.
     * \param visited  Internal recursion guard; root callers leave this null.
     * \param depth    Internal recursion depth; root callers leave this at 0. Throws once a
     *                 chain of distinct dependency files would recurse past a fixed limit —
     *                 the \a visited cycle guard alone doesn't bound a long, non-cyclic chain.
     */
    static void copyPandaFile(const QFileInfo &srcPath, const QFileInfo &destPath,
                              QSet<QString> *visited = nullptr, int depth = 0);

    // --- Magic Headers ---

    constexpr static quint32 MAGIC_HEADER_CIRCUIT = 0x57504346; // "WPCF" (wiRedPanda Circuit File)
    constexpr static quint32 MAGIC_HEADER_WAVEFORM = 0x57505746; // "WPWF" (wiRedPanda Waveform File)
};
