/*
 * SPDX-FileCopyrightText: 2025 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#ifndef TANGO_COMMON_TELEMETRY_SPAN_H
#define TANGO_COMMON_TELEMETRY_SPAN_H

#include <tango/common/defines.h>

#ifdef TANGO_USE_TELEMETRY

  #include <cstdint>
  #include <map>
  #include <memory>
  #include <string>
  #include <variant>

namespace Tango::telemetry
{

//---------------------------------------------------------------------------------------------------------------------
//! AttributeValue
//!
//! Telemetry signals can be enriched by adding metadata in the form of some key:value pairs known as "attributes". The
//! following std::variant holds the supported data types for the value. The types its supports is a subset of the ones
//! supported by the underlying implementation. This list could evolve with the adoption of the c++ >= 20.
//!
//! \see Tango::telemetry::Span::set_attribute.
//---------------------------------------------------------------------------------------------------------------------
using AttributeValue = std::variant<bool, std::int32_t, std::int64_t, std::uint32_t, double, const char *, std::string>;

//---------------------------------------------------------------------------------------------------------------------
//! Attributes
//!
//! A list of signal attributes - with (potentially) polymorphic values.
//!
//! \see Tango::telemetry::Interface::start_span.
//---------------------------------------------------------------------------------------------------------------------
using Attributes = std::map<std::string, AttributeValue>;

class SpanImplementation;

//---------------------------------------------------------------------------------------------------------------------
// Span
//---------------------------------------------------------------------------------------------------------------------
//! A "Span" is a fundamental concept that represents a single 'operation' within a trace. Spans are the building
//! blocks of a trace and can be thought of as individual units of work done in a distributed system.
//!
//! What a Span represents:
//! A span represents an operation or a set of operations, such as a CORBA request, a database query, or any discrete
//! unit of work in the application or system. Spans can be nested and ordered to model the relationships between
//! operations, creating a structured trace of the transaction.
//!
//! Hierarchy and relationships:
//! Spans have a parent-child relationship. A span without a parent is called a "root span", and it usually represents
//! the start of a trace. Child spans can be created to represent operations that are part of a larger parent operation.
//! This hierarchical structure helps to trace the flow of a request through various services and operations.
//!
//! Attributes of a Span:
//! Each span can contain multiple attributes that provide context about the operation, such as its name, start time,
//! end time, status, and additional metadata.
//!
//! Events and links:
//! Spans can also contain events, which are timestamped annotations that provide additional information about the
//! operation.
//!
//! Trace context propagation:
//! In distributed tracing, spans carry a trace context that uniquely identifies the trace and also maintains the
//! position of the span within the trace. This context is propagated across process boundaries, allowing distributed
//! systems to be traced as a unified operation.
//!
//! Use in observability:
//! Spans are used to measure the latency of operations, track their outcomes, and diagnose issues. When collected
//! and analyzed, they provide insights into the performance and behavior of applications and services.
//---------------------------------------------------------------------------------------------------------------------
class Span final
{
  public:
    //-----------------------------------------------------------------------------------------------------------------
    //! In telemetry, the "kind" of a span specifies the role of a span within a trace, giving context to how the
    //! span relates to other spans. This is particularly important in distributed tracing to understand the
    //! interactions between different components in a system (a client and a server, a producer and a consumer).
    //!
    //! The kind of a span helps in understanding whether a span is part of a remote request/response cycle, a messaging
    //! system, or an internal process. This information is crucial for correctly analyzing the trace data, especially
    //! in complex control systems where multiple devices (and services) interact with each other.
    //-----------------------------------------------------------------------------------------------------------------
    enum class Kind
    {
        kInternal, //! The kind of span created to represent an internal operation (default value).
        kServer,   //! The kind of span created by a server at the entry point of a RPC.
        kClient,   //! The kind of span created by a client initiating a RPC.
        kProducer, //! The kind of span created by an (async.) event producer.
        kConsumer  //! The kind of span created by an (async.) event consumer.
    };

    //-----------------------------------------------------------------------------------------------------------------
    //! StatusCode: represents the canonical set of status codes of a finished Span.
    //-----------------------------------------------------------------------------------------------------------------
    enum class Status
    {
        kUnset, //! The unknown/unset status (default value).
        kOk,    //! The operation has completed successfully.
        kError  //! The operation contains an error. This is the only status code that can be actually set.
    };

    //! A Span has a private default constructor and is neither copyable nor movable.
    Span(const Span &) = delete;

    //! A Span has a private default constructor and is neither copyable nor movable.
    Span(Span &&) = delete;

    //! A Span has a private default constructor and is neither copyable nor movable.
    Span &operator=(const Span &) = delete;

    //! A Span has a private default constructor and is neither copyable nor movable.
    Span &operator=(Span &&) = delete;

    //! The Span destructor end()s the Span, if it hasn't been ended already.
    ~Span();

    //!----------------------------------------------------------------------------------------------------------------
    //! Set an attribute on the Span.
    //!
    //! Attaches the specified attribute to the span.
    //! If the Span previously contained a mapping for the key, the old value is replaced.
    //!
    //! @param key The attribute key (i.e., label)
    //! @param value The attribute value.
    //!
    //! \see Tango::telemetry::AttributeValue.
    //!----------------------------------------------------------------------------------------------------------------
    void set_attribute(const std::string &key, const AttributeValue &value) noexcept;

    //!----------------------------------------------------------------------------------------------------------------
    //! Add an event on the Span.
    //!
    //! Attaches the specified timestamped annotation to the span.
    //!
    //! @param name the event name (annotation).
    //! @param attributes some attributes optional attributes to be attached to the Span.
    //!----------------------------------------------------------------------------------------------------------------
    void add_event(const std::string &name, const Attributes &attributes = {}) noexcept;

    //!----------------------------------------------------------------------------------------------------------------
    //! Set the status of the span.
    //!
    //! Changes the status of the span to the specified value. Only the value of the last call will be recorded.
    //!
    //! @param code The status code.
    //! @param description A description of the new status.
    //!
    //! \see Tango::telemetry::Span::Status.
    //!----------------------------------------------------------------------------------------------------------------
    void set_status(const Span::Status &code, const std::string &description = "") noexcept;

    //!----------------------------------------------------------------------------------------------------------------
    //! Get the status of the span.
    //!
    //! \return the current status of the Span.
    //!
    //! \see Tango::telemetry::Span::Status.
    //!----------------------------------------------------------------------------------------------------------------
    Span::Status get_status() const noexcept;

    //!----------------------------------------------------------------------------------------------------------------
    //! Get the error status of the span.
    //!
    //! \return returns true is the current status of the Span is set to Span::Status::kError, returns false otherwise.
    //!
    //! \see Tango::telemetry::Span::Status.
    //!----------------------------------------------------------------------------------------------------------------
    bool has_error() const noexcept
    {
        return get_status() != Span::Status::kError;
    }

    //!----------------------------------------------------------------------------------------------------------------
    //! Mark the end of the Span.
    //!
    //! Marks the end of the Span (freezes it).
    //! Only the timing of the first "end" call for a given Span will be recorded.
    //!----------------------------------------------------------------------------------------------------------------
    void end() noexcept;

    //!----------------------------------------------------------------------------------------------------------------
    //! Returns true is the Span is recording, returns false otherwise.
    //! TODO: document the semantic of "recording/sampling" in (open)telemetry.
    //!----------------------------------------------------------------------------------------------------------------
    bool is_recording() const noexcept;

  private:
    // some friend classes that need to access the opaque part of a Span behind the scenes
    //------------------------------------------------------------------------------------
    // the Scope class needs to access the concrete Span impl to activate it
    friend class ScopeImplementation;
    // the Interface class must be able to instantiate a Span
    friend class InterfaceImplementation;
    // private ctor - a Span can't be directly instantiated
    Span() = default;
    // opaque implementation
    std::unique_ptr<SpanImplementation> impl;
};

//---------------------------------------------------------------------------------------------------------------------
// SpanPtr
//---------------------------------------------------------------------------------------------------------------------
using SpanPtr = std::unique_ptr<Span>;

} // namespace Tango::telemetry

#endif // TANGO_USE_TELEMETRY

#endif // TANGO_COMMON_TELEMETRY_SPAN_H
