// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#pragma once

#include <utils/filepath.h>
#include <utils/result.h>

#include <QDateTime>
#include <QHash>
#include <QLatin1StringView>
#include <QList>

#include <functional>
#include <utility>

namespace QmlProfiler::Internal {

// Name of the CTF2 data stream class that marks a call-stack sampler trace.
inline constexpr QLatin1StringView samplerStreamName = QLatin1StringView("sampler-stacks");

// In-memory representation of one sampling session: every thread's call stack
// at every sampling tick, plus whether the thread was running at that moment.
class SampleTraceData
{
public:
    struct ThreadSample
    {
        quint64 tsUs = 0;     // microseconds since recording start
        quint64 tid = 0;
        bool running = false; // scheduler run state at capture (best-effort "on-CPU")
        QList<int> frames;    // root-first indices into `labels`; may be empty

        friend bool operator==(const ThreadSample &, const ThreadSample &) = default;
    };

    struct Label
    {
        QString name;
        QString file;       // source path from debug info; empty if unavailable
        int line = 0;
        QString module;     // binary/dylib name; empty if no loaded image
        quint64 offset = 0; // offset within the module (or the address if no image)
        // Original virtual address for post-capture symbol resolution. Only
        // meaningful in-process, before the trace is written: writeSampleTrace()
        // does not persist it, so labels read back from disk carry 0 here.
        quint64 address = 0;

        Label() = default;
        Label(const char *n) : name(QString::fromUtf8(n)) {}
        Label(QString n, QString f = {}, int l = 0, QString m = {}, quint64 o = 0)
            : name(std::move(n)), file(std::move(f)), line(l), module(std::move(m)), offset(o) {}

        // A QML/JS frame spliced in from the QML profiler (see mergeQmlIntoSamples):
        // it has no loaded image and a QML/JS source file. Native C++ frames always
        // carry a module, so this cleanly tells the two apart for colouring.
        bool isJs() const
        {
            return module.isEmpty()
                   && (file.endsWith(QLatin1String(".qml"), Qt::CaseInsensitive)
                       || file.endsWith(QLatin1String(".js"), Qt::CaseInsensitive));
        }

        friend bool operator==(const Label &, const Label &) = default;
    };

    quint64 pid = 0;
    QList<Label> labels;                 // index = label id
    QHash<quint64, QString> threadNames; // tid -> name (entries may be empty)
    QList<ThreadSample> samples;         // ordered by tsUs

    friend bool operator==(const SampleTraceData &, const SampleTraceData &) = default;
};

// Writes `data` as a CTF2 trace ("metadata" + "stream0" files) into the
// existing directory `dir`. `progress`, if set, is called with 0..100 while
// the samples are written.
Utils::Result<> writeSampleTrace(const SampleTraceData &data, const Utils::FilePath &dir,
                                 const std::function<void(int)> &progress = {});

// Reads back a trace written by writeSampleTrace. `progress`, if set, is called
// with 0..100 as the stream is decoded; a sampler stream routinely runs to
// hundreds of megabytes, so a caller on a worker thread wants to report it.
Utils::Result<SampleTraceData> readSampleTrace(const Utils::FilePath &dir,
                                               const std::function<void(int)> &progress = {});

// True if `dir` holds a CTF2 trace whose schema contains a data stream class
// named samplerStreamName. Reads only the metadata file.
bool isSamplerTrace(const Utils::FilePath &dir);

// Path of a not-yet-existing trace in the temporary location, named after
// `prefix`, `now` and `suffix`, for example
// ".../qtprofiler-sample-2026-08-11-14-32-07". The traces pile up there, so the
// timestamp is spelled out rather than counted in milliseconds since the epoch.
// A counter is appended if a trace of the same second is already present.
// Split out from uniqueTracePath() so a test can exercise collision handling
// for a fixed point in time instead of racing the real clock.
Utils::FilePath uniqueTracePathAt(const QDateTime &now, QLatin1StringView prefix,
                                  QLatin1StringView suffix = {});

// uniqueTracePathAt() for the current local time.
Utils::FilePath uniqueTracePath(QLatin1StringView prefix, QLatin1StringView suffix = {});

} // namespace QmlProfiler::Internal
