// This file Copyright © Transmission authors and contributors.
// It may be used under the 3-Clause BSD (SPDX: BSD-3-Clause),
// GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.

// This file defines the public API for the libtransmission library.

#pragma once

// --- Basic Types

#include <array>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "libtransmission/tr-macros.h"
#include "libtransmission/values.h"

using tr_file_index_t = size_t;
using tr_piece_index_t = uint32_t;
/* Assuming a 16 KiB block (tr_block_info::BlockSize), a 32-bit block index gives us a maximum torrent size of 64 TiB.
 * When we ever need to grow past that, change tr_block_index_t and  tr_piece_index_t to uint64_t. */
using tr_block_index_t = uint32_t;
using tr_byte_index_t = uint64_t;
using tr_tracker_tier_t = uint32_t;
using tr_tracker_id_t = uint32_t;
using tr_torrent_id_t = int;
using tr_mode_t = uint16_t;

inline auto constexpr TrDefaultBlocklistFilename = std::string_view{ "blocklist.bin" };
inline auto constexpr TrDefaultHttpServerBasePath = std::string_view{ "/transmission/" };
inline auto constexpr TrDefaultPeerLimitGlobal = 200U;
inline auto constexpr TrDefaultPeerLimitTorrent = 50U;
inline auto constexpr TrDefaultPeerPort = 51413U;
inline auto constexpr TrDefaultPeerSocketTos = std::string_view{ "le" };
inline auto constexpr TrDefaultRpcPort = 9091U;
inline auto constexpr TrDefaultRpcWhitelist = std::string_view{ "127.0.0.1,::1" };

inline auto constexpr TrHttpServerRpcRelativePath = std::string_view{ "rpc" };
inline auto constexpr TrHttpServerWebRelativePath = std::string_view{ "web/" };
inline auto constexpr TrRpcSessionIdHeader = std::string_view{ "X-Transmission-Session-Id" };
inline auto constexpr TrRpcVersionHeader = std::string_view{ "X-Transmission-Rpc-Version" };

struct tr_block_span_t
{
    tr_block_index_t begin;
    tr_block_index_t end;
};

struct tr_byte_span_t
{
    uint64_t begin;
    uint64_t end;
};

struct tr_ctor;
struct tr_error;
struct tr_session;
struct tr_torrent;
struct tr_torrent_metainfo;
struct tr_variant;

enum tr_verify_added_mode : uint8_t
{
    // See discussion @ https://github.com/transmission/transmission/pull/2626
    // Let newly-added torrents skip upfront verify do it on-demand later.
    TR_VERIFY_ADDED_FAST = 0,

    // Force torrents to be fully verified as they are added.
    TR_VERIFY_ADDED_FULL = 1
};

enum tr_encryption_mode : uint8_t
{
    TR_CLEAR_PREFERRED,
    TR_ENCRYPTION_PREFERRED,
    TR_ENCRYPTION_REQUIRED
};

enum tr_priority_t : int8_t
{
    TR_PRI_LOW = -1,
    TR_PRI_NORMAL = 0, /* since Normal is 0, memset initializes nicely */
    TR_PRI_HIGH = 1
};

enum : int8_t
{
    TR_RATIO_NA = -1,
    TR_RATIO_INF = -2
};

// --- Startup & Shutdown

/**
 * @addtogroup tr_session Session
 *
 * A libtransmission session is created by calling `tr_sessionInit()`.
 * libtransmission creates a thread for itself so that it can operate
 * independently of the caller's event loop. The session will continue
 * until `tr_sessionClose()` is called.
 *
 * @{
 */

/**
 * @brief get Transmission's default configuration file directory.
 *
 * The default configuration directory is determined this way:
 * -# If the `TRANSMISSION_HOME` environment variable is set, its value is used.
 * -# On Darwin, `"${HOME}/Library/Application Support/${appname}"` is used.
 * -# On Windows, `"${CSIDL_APPDATA}/${appname}"` is used.
 * -# If `XDG_CONFIG_HOME` is set, `"${XDG_CONFIG_HOME}/${appname}"` is used.
 * -# `"${HOME}/.config/${appname}"` is used as a last resort.
 */
[[nodiscard]] std::string tr_getDefaultConfigDir(std::string_view appname);

/**
 * @brief returns Transmission's default download directory.
 *
 * The default download directory is determined this way:
 * -# If the `HOME` environment variable is set, `"${HOME}/Downloads"` is used.
 * -# On Windows, `"${CSIDL_MYDOCUMENTS}/Downloads"` is used.
 * -# Otherwise, `getpwuid(getuid())->pw_dir + "/Downloads"` is used.
 */
[[nodiscard]] std::string tr_getDefaultDownloadDir();

/**
 * Add libtransmission's default settings to the benc dictionary.
 *
 * Example:
 * @code
 *     int64_t i;
 *
 *     auto settings = tr_sessionGetDefaultSettings();
 *     if (tr_variantDictFindInt(&settings, TR_PREFS_KEY_PEER_PORT, &i))
 *         fprintf(stderr, "the default peer port is %d\n", (int)i);
 * @endcode
 *
 * @return a variant map of the default settinggs
 * @see `tr_sessionLoadSettings()`
 * @see `tr_sessionInit()`
 * @see `tr_getDefaultConfigDir()`
 */
tr_variant tr_sessionGetDefaultSettings();

/**
 * Add the session's current configuration settings to the benc dictionary.
 *
 * TODO: if we ever make libtransmissionapp, this would go there.
 *
 * @return a variant map of the session's current settings
 * @param session the session to query
 * @see `tr_sessionGetDefaultSettings()`
 */
tr_variant tr_sessionGetSettings(tr_session const* session);

/**
 * Load settings from the configuration directory's settings.json file,
 * using libtransmission's default settings as fallbacks for missing keys.
 *
 * TODO: if we ever make libtransmissionapp, this would go there.
 *
 * @param config_dir the configuration directory to find settings.json
 * @param app_defaults optional tr_variant containing the app-specific defaults
 * @return the loaded settings
 * @see `tr_sessionGetDefaultSettings()`
 * @see `tr_sessionInit()`
 * @see `tr_sessionSaveSettings()`
 */
[[nodiscard]] tr_variant tr_sessionLoadSettings(std::string_view config_dir, tr_variant const* app_defaults = nullptr);

/**
 * Add the session's configuration settings to the benc dictionary
 * and save it to the configuration directory's settings.json file.
 *
 * TODO: if we ever make libtransmissionapp, this would go there.
 *
 * @param session    the session to save
 * @param config_dir  the directory to write to
 * @param client_settings the dictionary to save
 * @see `tr_sessionLoadSettings()`
 */
void tr_sessionSaveSettings(tr_session* session, std::string_view config_dir, tr_variant const& client_settings);

/**
 * @brief Initialize a libtransmission session.
 *
 * For example, this will instantiate a session with all the default values:
 * @code
 *     auto settings = tr_sessionGetDefaultSettings();
 *     char const* const configDir = tr_getDefaultConfigDir("Transmission");
 *     tr_session* const session = tr_sessionInit(configDir, true, &settings);
 * @endcode
 *
 * @param config_dir where Transmission will look for resume files, blocklists, etc.
 * @param message_queueing_enabled if false, messages will be dumped to stderr
 * @param settings libtransmission settings
 * @see `tr_sessionGetDefaultSettings()`
 * @see `tr_sessionLoadSettings()`
 * @see `tr_getDefaultConfigDir()`
 */
tr_session* tr_sessionInit(std::string_view config_dir, bool message_queueing_enabled, tr_variant const& settings);

/** @brief Update a session's settings from a benc dictionary
           like to the one used in `tr_sessionInit()` */
void tr_sessionSet(tr_session* session, tr_variant const& settings);

/** @brief Rescan the blocklists directory and
           reload whatever blocklist files are found there */
void tr_sessionReloadBlocklists(tr_session* session);

/**
 * @brief End a libtransmission session.
 * @see `tr_sessionInit()`
 *
 * This may take some time while &event=stopped announces are sent to trackers.
 *
 * @param timeout_secs specifies how long to wait on these announces.
 */
void tr_sessionClose(tr_session* session, double timeout_secs = 15.0);

/**
 * @brief Return the session's configuration directory.
 *
 * This is where transmission stores its torrent files, .resume files,
 * blocklists, etc. It's set in `tr_transmissionInit()`.
 */
[[nodiscard]] std::string tr_sessionGetConfigDir(tr_session const* session);

/**
 * @brief Get the default download folder for new torrents.
 *
 * This is set by `tr_sessionInit()` or `tr_sessionSetDownloadDir()`,
 * and can be overridden on a per-torrent basis by `tr_ctorSetDownloadDir()`.
 */
[[nodiscard]] std::string tr_sessionGetDownloadDir(tr_session const* session);

/**
 * @brief Set the per-session default download folder for new torrents.
 * @see `tr_sessionInit()`
 * @see `tr_sessionGetDownloadDir()`
 * @see `tr_ctorSetDownloadDir()`
 */
void tr_sessionSetDownloadDir(tr_session* session, std::string_view download_dir);

/** @brief get the per-session incomplete download folder */
[[nodiscard]] std::string tr_sessionGetIncompleteDir(tr_session const* session);

/**
 * @brief set the per-session incomplete download folder.
 *
 * When you add a new torrent and the session's incomplete directory is enabled,
 * the new torrent will start downloading into that directory, and then be moved
 * to `tr_torrent.downloadDir` when the torrent is finished downloading.
 *
 * Torrents aren't moved as a result of changing the session's incomplete dir --
 * it's applied to new torrents, not existing ones.
 *
 * `tr_torrentSetLocation()` overrules the incomplete dir: when a user specifies
 * a new location, that becomes the torrent's new `download_dir` and the torrent
 * is moved there immediately regardless of whether or not it's complete.
 *
 * @see `tr_sessionInit()`
 * @see `tr_sessionGetIncompleteDir()`
 * @see `tr_sessionSetIncompleteDirEnabled()`
 * @see `tr_sessionGetIncompleteDirEnabled()`
 */
void tr_sessionSetIncompleteDir(tr_session* session, std::string_view dir);

/** @brief get whether or not the incomplete download folder is enabled */
bool tr_sessionIsIncompleteDirEnabled(tr_session const* session);

/** @brief enable or disable use of the incomplete download folder */
void tr_sessionSetIncompleteDirEnabled(tr_session* session, bool enabled);

/** @brief return true if files will end in ".part" until they're complete */
bool tr_sessionIsIncompleteFileNamingEnabled(tr_session const* session);

/**
 * @brief When enabled, newly-created files will have ".part" appended
 *        to their filename until the file is fully downloaded
 *
 * This is not retroactive -- toggling this will not rename existing files.
 * It only applies to new files created by Transmission after this API call.
 *
 * @see `tr_sessionIsIncompleteFileNamingEnabled()`
 */
void tr_sessionSetIncompleteFileNamingEnabled(tr_session* session, bool enabled);

/** @brief Get whether or not RPC calls are allowed in this session.
    @see `tr_sessionInit()`
    @see `tr_sessionSetRPCEnabled()` */
bool tr_sessionIsRPCEnabled(tr_session const* session);

/**
 * @brief Set whether or not RPC calls are allowed in this session.
 *
 * @details If true, libtransmission will open a server socket to listen
 * for incoming http RPC requests as described in docs/rpc-spec.md.
 *
 * This is initially set by `tr_sessionInit()` and can be
 * queried by `tr_sessionIsRPCEnabled()`.
 */
void tr_sessionSetRPCEnabled(tr_session* session, bool is_enabled);

/** @brief Get which port to listen for RPC requests on.
    @see `tr_sessionInit()`
    @see `tr_sessionSetRPCPort` */
uint16_t tr_sessionGetRPCPort(tr_session const* session);

/** @brief Specify which port to listen for RPC requests on.
    @see `tr_sessionInit()`
    @see `tr_sessionGetRPCPort` */
void tr_sessionSetRPCPort(tr_session* session, uint16_t port);

/** @brief get the Access Control List for allowing/denying RPC requests.
    @return a comma-separated string of whitelist domains.
    @see `tr_sessionInit`
    @see `tr_sessionSetRPCWhitelist` */
[[nodiscard]] std::string tr_sessionGetRPCWhitelist(tr_session const* session);

/**
 * @brief Specify a whitelist for remote RPC access
 *
 * The whitelist is a comma-separated list of dotted-quad IP addresses
 * to be allowed. Wildmat notation is supported, meaning that
 * `'?'` is interpreted as a single-character wildcard and
 * `'*'` is interpreted as a multi-character wildcard.
 */
void tr_sessionSetRPCWhitelist(tr_session* session, std::string_view whitelist);

bool tr_sessionGetRPCWhitelistEnabled(tr_session const* session);
void tr_sessionSetRPCWhitelistEnabled(tr_session* session, bool is_enabled);

// TODO(ckerr): rename function to indicate it returns the salted value
/** @brief get the salted version of the password used to restrict RPC requests.
    @return the password string.
    @see `tr_sessionInit()`
    @see `tr_sessionSetRPCPassword()` */
[[nodiscard]] std::string tr_sessionGetRPCPassword(tr_session const* session);
void tr_sessionSetRPCPassword(tr_session* session, std::string_view password);

[[nodiscard]] std::string tr_sessionGetRPCUsername(tr_session const* session);
void tr_sessionSetRPCUsername(tr_session* session, std::string_view username);

bool tr_sessionIsRPCPasswordEnabled(tr_session const* session);
void tr_sessionSetRPCPasswordEnabled(tr_session* session, bool is_enabled);

void tr_sessionSetDefaultTrackers(tr_session* session, std::string_view trackers);

enum tr_rpc_callback_type : uint8_t
{
    TR_RPC_TORRENT_ADDED,
    TR_RPC_TORRENT_STARTED,
    TR_RPC_TORRENT_STOPPED,
    TR_RPC_TORRENT_REMOVING,
    TR_RPC_TORRENT_TRASHING, /* _REMOVING + delete local data */
    TR_RPC_TORRENT_CHANGED, /* catch-all for the "torrent_set" rpc method */
    TR_RPC_TORRENT_MOVED,
    TR_RPC_SESSION_CHANGED,
    TR_RPC_SESSION_QUEUE_POSITIONS_CHANGED, /* catch potentially multiple torrents being moved in the queue */
    TR_RPC_SESSION_CLOSE
};

enum tr_rpc_callback_status : uint8_t
{
    /* no special handling is needed by the caller */
    TR_RPC_OK = 0,
    /* indicates to the caller that the client will take care of
     * removing the torrent itself. For example the client may
     * need to keep the torrent alive long enough to cleanly close
     * some resources in another thread. */
    TR_RPC_NOREMOVE = (1 << 1)
};

using tr_rpc_func = tr_rpc_callback_status (*)( //
    tr_session* session,
    tr_rpc_callback_type type,
    struct tr_torrent* tor_or_null,
    void* user_data);

/**
 * Register to be notified whenever something is changed via RPC,
 * such as a torrent being added, removed, started, stopped, etc.
 *
 * func is invoked FROM LIBTRANSMISSION'S THREAD!
 * This means func must be fast (to avoid blocking peers),
 * shouldn't call libtransmission functions (to avoid deadlock),
 * and shouldn't modify client-level memory without using a mutex!
 */
void tr_sessionSetRPCCallback(tr_session* session, tr_rpc_func func, void* user_data);

// ---

/** @brief Used by `tr_sessionGetStats()` and `tr_sessionGetCumulativeStats()` */
struct tr_session_stats
{
    float ratio; /* TR_RATIO_INF, TR_RATIO_NA, or total up/down */
    uint64_t uploadedBytes; /* total up */
    uint64_t downloadedBytes; /* total down */
    uint64_t filesAdded; /* number of files added */
    uint64_t sessionCount; /* program started N times */
    uint64_t secondsActive; /* how long Transmission's been running */
};

/** @brief Get bandwidth use statistics for the current session */
tr_session_stats tr_sessionGetStats(tr_session const* session);

/** @brief Get cumulative bandwidth statistics for current and past sessions */
tr_session_stats tr_sessionGetCumulativeStats(tr_session const* session);

void tr_sessionClearStats(tr_session* session);

/**
 * @brief Set whether or not torrents are allowed to do peer exchanges.
 *
 * PEX is always disabled in private torrents regardless of this.
 * In public torrents, PEX is enabled by default.
 */
bool tr_sessionIsPexEnabled(tr_session const* session);
void tr_sessionSetPexEnabled(tr_session* session, bool is_enabled);

bool tr_sessionIsDHTEnabled(tr_session const* session);
void tr_sessionSetDHTEnabled(tr_session* session, bool is_enabled);

bool tr_sessionIsUTPEnabled(tr_session const* session);
void tr_sessionSetUTPEnabled(tr_session* session, bool is_enabled);

bool tr_sessionIsLPDEnabled(tr_session const* session);
void tr_sessionSetLPDEnabled(tr_session* session, bool is_enabled);

size_t tr_sessionGetCacheLimit_MB(tr_session const* session);
void tr_sessionSetCacheLimit_MB(tr_session* session, size_t mbytes);

tr_encryption_mode tr_sessionGetEncryption(tr_session const* session);
void tr_sessionSetEncryption(tr_session* session, tr_encryption_mode mode);

// --- Incoming Peer Connections Port

bool tr_sessionIsPortForwardingEnabled(tr_session const* session);
void tr_sessionSetPortForwardingEnabled(tr_session* session, bool enabled);

uint16_t tr_sessionGetPeerPort(tr_session const* session);
void tr_sessionSetPeerPort(tr_session* session, uint16_t port);

uint16_t tr_sessionSetPeerPortRandom(tr_session* session);

bool tr_sessionGetPeerPortRandomOnStart(tr_session const* session);
void tr_sessionSetPeerPortRandomOnStart(tr_session* session, bool random);

enum tr_port_forwarding_state : uint8_t
{
    TR_PORT_ERROR,
    TR_PORT_UNMAPPED,
    TR_PORT_UNMAPPING,
    TR_PORT_MAPPING,
    TR_PORT_MAPPED
};

tr_port_forwarding_state tr_sessionGetPortForwarding(tr_session const* session);

enum class tr_direction : uint8_t
{
    ClientToPeer = 0,
    Up = 0,
    PeerToClient = 1,
    Down = 1,
};

// --- Session primary speed limits

size_t tr_sessionGetSpeedLimit_KBps(tr_session const* session, tr_direction dir);
void tr_sessionSetSpeedLimit_KBps(tr_session* session, tr_direction dir, size_t limit_kbyps);

bool tr_sessionIsSpeedLimited(tr_session const* session, tr_direction dir);
void tr_sessionLimitSpeed(tr_session* session, tr_direction dir, bool limited);

// --- Session alt speed limits

size_t tr_sessionGetAltSpeed_KBps(tr_session const* session, tr_direction dir);
void tr_sessionSetAltSpeed_KBps(tr_session* session, tr_direction dir, size_t limit_kbyps);

bool tr_sessionUsesAltSpeed(tr_session const* session);
void tr_sessionUseAltSpeed(tr_session* session, bool enabled);

bool tr_sessionUsesAltSpeedTime(tr_session const* session);
void tr_sessionUseAltSpeedTime(tr_session* session, bool enabled);

size_t tr_sessionGetAltSpeedBegin(tr_session const* session);
void tr_sessionSetAltSpeedBegin(tr_session* session, size_t minutes_since_midnight);

size_t tr_sessionGetAltSpeedEnd(tr_session const* session);
void tr_sessionSetAltSpeedEnd(tr_session* session, size_t minutes_since_midnight);

enum tr_sched_day : uint8_t
{
    TR_SCHED_SUN = (1 << 0),
    TR_SCHED_MON = (1 << 1),
    TR_SCHED_TUES = (1 << 2),
    TR_SCHED_WED = (1 << 3),
    TR_SCHED_THURS = (1 << 4),
    TR_SCHED_FRI = (1 << 5),
    TR_SCHED_SAT = (1 << 6),
    TR_SCHED_WEEKDAY = (TR_SCHED_MON | TR_SCHED_TUES | TR_SCHED_WED | TR_SCHED_THURS | TR_SCHED_FRI),
    TR_SCHED_WEEKEND = (TR_SCHED_SUN | TR_SCHED_SAT),
    TR_SCHED_ALL = (TR_SCHED_WEEKDAY | TR_SCHED_WEEKEND)
};

tr_sched_day tr_sessionGetAltSpeedDay(tr_session const* session);
void tr_sessionSetAltSpeedDay(tr_session* session, tr_sched_day day);

using tr_altSpeedFunc = void (*)(tr_session* session, bool active, bool user_driven, void*);

void tr_sessionSetAltSpeedFunc(tr_session* session, tr_altSpeedFunc func, void* user_data);

// ---

double tr_sessionGetRawSpeed_KBps(tr_session const* session, tr_direction dir);

bool tr_sessionIsRatioLimited(tr_session const* session);
void tr_sessionSetRatioLimited(tr_session* session, bool is_limited);

double tr_sessionGetRatioLimit(tr_session const* session);
void tr_sessionSetRatioLimit(tr_session* session, double desired_ratio);

bool tr_sessionIsIdleLimited(tr_session const* session);
void tr_sessionSetIdleLimited(tr_session* session, bool is_limited);

uint16_t tr_sessionGetIdleLimit(tr_session const* session);
void tr_sessionSetIdleLimit(tr_session* session, uint16_t idle_minutes);

uint16_t tr_sessionGetPeerLimit(tr_session const* session);
void tr_sessionSetPeerLimit(tr_session* session, uint16_t max_global_peers);

uint16_t tr_sessionGetPeerLimitPerTorrent(tr_session const* session);
void tr_sessionSetPeerLimitPerTorrent(tr_session* session, uint16_t max_peers);

bool tr_sessionGetPaused(tr_session const* session);
void tr_sessionSetPaused(tr_session* session, bool is_paused);

void tr_sessionSetDeleteSource(tr_session* session, bool delete_source);

tr_priority_t tr_torrentGetPriority(tr_torrent const* tor);
void tr_torrentSetPriority(tr_torrent* tor, tr_priority_t priority);

// ---

/**
 * Torrent Queueing
 *
 * There are independent queues for seeding (`tr_direction::Up`) and leeching (`tr_direction::Down`).
 *
 * If the session already has enough non-stalled seeds/leeches when
 * `tr_torrentStart()` is called, the torrent will be moved into the
 * appropriate queue and its state will be `TR_STATUS_{DOWNLOAD,SEED}_WAIT`.
 *
 * To bypass the queue and unconditionally start the torrent use
 * `tr_torrentStartNow()`.
 *
 * Torrents can be moved in the queue using the simple functions
 * `tr_torrentQueueMove{Top,Up,Down,Bottom}`. They can be moved to
 * arbitrary points in the queue with `tr_torrentSetQueuePosition()`.
 */

/** @brief Like `tr_torrentStart()`, but resumes right away regardless of the queues. */
void tr_torrentStartNow(tr_torrent* tor);

/** @brief Return the queued torrent's position in the queue it's in. [0...n) */
size_t tr_torrentGetQueuePosition(tr_torrent const* tor);

/** @brief Set the queued torrent's position in the queue it's in.
 * Edge cases: `pos <= 0` moves to the front; `pos >= queue's length` moves to the back */
void tr_torrentSetQueuePosition(tr_torrent* tor, size_t queue_position);

// ---

/** @brief Convenience function for moving a batch of torrents to the front of their queue(s) */
void tr_torrentsQueueMoveTop(tr_torrent* const* torrents, size_t torrent_count);

/** @brief Convenience function for moving a batch of torrents ahead one step in their queue(s) */
void tr_torrentsQueueMoveUp(tr_torrent* const* torrents, size_t torrent_count);

/** @brief Convenience function for moving a batch of torrents back one step in their queue(s) */
void tr_torrentsQueueMoveDown(tr_torrent* const* torrents, size_t torrent_count);

/** @brief Convenience function for moving a batch of torrents to the back of their queue(s) */
void tr_torrentsQueueMoveBottom(tr_torrent* const* torrents, size_t torrent_count);

// ---

/** @brief Return the number of torrents allowed to download (if direction is `tr_direction::Down`) or seed (if direction is `tr_direction::Up`) at the same time */
size_t tr_sessionGetQueueSize(tr_session const* session, tr_direction dir);

/** @brief Set the number of torrents allowed to download (if direction is `tr_direction::Down`) or seed (if direction is `tr_direction::Up`) at the same time */
void tr_sessionSetQueueSize(tr_session* session, tr_direction dir, size_t max_simultaneous_torrents);

/** @brief Return true if we're limiting how many torrents can concurrently download (`tr_direction::Down`) or seed (`tr_direction::Up`) at the same time */
bool tr_sessionGetQueueEnabled(tr_session const* session, tr_direction dir);

/** @brief Set whether or not to limit how many torrents can download (`tr_direction::Down`) or seed (`tr_direction::Up`) at the same time */
void tr_sessionSetQueueEnabled(tr_session* session, tr_direction dir, bool do_limit_simultaneous_torrents);

// ---

/** @return the number of minutes a torrent can be idle before being considered as stalled */
size_t tr_sessionGetQueueStalledMinutes(tr_session const* session);

/** @brief Consider torrent as 'stalled' when it's been inactive for N minutes.
    Stalled torrents are left running but are not counted by `tr_sessionGetQueueSize()`. */
void tr_sessionSetQueueStalledMinutes(tr_session* session, int minutes);

/** @return true if torrents idle for over N minutes will be flagged as 'stalled' */
bool tr_sessionGetQueueStalledEnabled(tr_session const* session);

/** @brief Set whether or not to count torrents idle for over N minutes as 'stalled' */
void tr_sessionSetQueueStalledEnabled(tr_session* session, bool enabled);

/** @brief Set a callback that is invoked when the queue starts a torrent */
void tr_sessionSetQueueStartCallback(tr_session* session, void (*callback)(tr_session*, tr_torrent*, void*), void* user_data);

// ---

/** @brief Set whether or not to verify data when torrent download is complete */
void tr_sessionSetCompleteVerifyEnabled(tr_session* session, bool enabled);

// ---

/**
 * Load all the torrents in the session's torrent folder.
 * This can be used at startup to kickstart all the torrents from the previous session.
 *
 * @return the number of torrents in the session
 */
size_t tr_sessionLoadTorrents(tr_session* session, tr_ctor* ctor);

/**
 * Get pointers to all the torrents in a session.
 *
 * Iff `buflen` is large enough to hold the torrents pointers,
 * then all of them are copied into `buf`.
 *
 * @return the number of torrents in the session
 */
size_t tr_sessionGetAllTorrents(tr_session* session, tr_torrent** buf, size_t buflen);

// ---

enum TrScript : uint8_t
{
    TR_SCRIPT_ON_TORRENT_ADDED,
    TR_SCRIPT_ON_TORRENT_DONE,
    TR_SCRIPT_ON_TORRENT_DONE_SEEDING,

    TR_SCRIPT_N_TYPES
};

[[nodiscard]] std::string tr_sessionGetScript(tr_session const* session, TrScript type);

void tr_sessionSetScript(tr_session* session, TrScript type, std::string_view script_filename);

bool tr_sessionIsScriptEnabled(tr_session const* session, TrScript type);

void tr_sessionSetScriptEnabled(tr_session* session, TrScript type, bool enabled);

/** @} */

// ---

/** @addtogroup Blocklists
    @{ */

/**
 * Specify a range of IPs for Transmission to block.
 *
 * Filename must be an uncompressed ascii file.
 *
 * libtransmission does not keep a handle to `filename`
 * after this call returns, so the caller is free to
 * keep or delete `filename` as it wishes.
 * libtransmission makes its own copy of the file
 * massaged into a binary format easier to search.
 *
 * The caller only needs to invoke this when the blocklist
 * has changed.
 */
size_t tr_blocklistSetContent(tr_session* session, std::string_view content_filename);

size_t tr_blocklistGetRuleCount(tr_session const* session);

bool tr_blocklistExists(tr_session const* session);

bool tr_blocklistIsEnabled(tr_session const* session);

void tr_blocklistSetEnabled(tr_session* session, bool is_enabled);

[[nodiscard]] std::string tr_blocklistGetURL(tr_session const* session);

/** @brief The blocklist that gets updated when an RPC client
           invokes the "blocklist_update" method */
void tr_blocklistSetURL(tr_session* session, std::string_view url);

/** @} */

/**
 * Instantiating tr_torrents and wrangling torrent file metadata
 *
 * 1. Torrent metadata is handled in the `tr_torrent_metadata` class.
 *
 * 2. Torrents should be instantiated using a torrent builder (`tr_ctor`).
 * Calling one of the `tr_ctorSetMetainfo*()` functions is required.
 * Other settings, e.g. torrent priority, are optional.
 * When ready, pass the builder object to `tr_torrentNew()`.
 */

enum tr_ctorMode : uint8_t
{
    TR_FALLBACK, /* indicates the ctor value should be used only in case of missing resume settings */
    TR_FORCE /* indicates the ctor value should be used regardless of what's in the resume settings */
};

/** @brief Create a torrent constructor object used to instantiate a `tr_torrent`
    @param session the tr_session. */
tr_ctor* tr_ctorNew(tr_session* session);

/** @brief Free a torrent constructor object */
void tr_ctorFree(tr_ctor* ctor);

/** @brief Get the "delete torrent file" flag from this peer constructor */
bool tr_ctorGetDeleteSource(tr_ctor const* ctor, bool* setme_do_delete);

/** @brief Set whether or not to delete the source torrent file
           when the torrent is added. (Default: False) */
void tr_ctorSetDeleteSource(tr_ctor* ctor, bool delete_source);

/** @brief Set the constructor's metainfo from a magnet link */
bool tr_ctorSetMetainfoFromMagnetLink(tr_ctor* ctor, std::string_view magnet, tr_error* error = nullptr);

tr_torrent_metainfo const* tr_ctorGetMetainfo(tr_ctor const* ctor);

/** @brief Set the constructor's metainfo from a raw benc already in memory */
bool tr_ctorSetMetainfo(tr_ctor* ctor, char const* metainfo, size_t len, tr_error* error);

/** @brief Set the constructor's metainfo from a local torrent file */
bool tr_ctorSetMetainfoFromFile(tr_ctor* ctor, std::string_view filename, tr_error* error = nullptr);

/** @brief Get this peer constructor's peer limit */
bool tr_ctorGetPeerLimit(tr_ctor const* ctor, tr_ctorMode mode, uint16_t* setme_count);

/** @brief Set how many peers this torrent can connect to. (Default: 50) */
void tr_ctorSetPeerLimit(tr_ctor* ctor, tr_ctorMode mode, uint16_t limit);

/** @brief Get the download path from this peer constructor */
std::optional<std::string> tr_ctorGetDownloadDir(tr_ctor const* ctor, tr_ctorMode mode);

/** @brief Set the download folder for the torrent being added with this ctor.
    @see `tr_sessionInit()` */
void tr_ctorSetDownloadDir(tr_ctor* ctor, tr_ctorMode mode, std::string_view dir);

/**
 * @brief Set the incompleteDir for this torrent.
 *
 * This is not a supported API call.
 * It only exists so the mac client can migrate
 * its older incompleteDir settings, and that's
 * the only place where it should be used.
 */
void tr_ctorSetIncompleteDir(tr_ctor* ctor, std::string_view dir);

/** @brief Get the "isPaused" flag from this peer constructor */
bool tr_ctorGetPaused(tr_ctor const* ctor, tr_ctorMode mode, bool* setme_is_paused);

/** Set whether or not the torrent begins downloading/seeding when created.
  (Default: not paused) */
void tr_ctorSetPaused(tr_ctor* ctor, tr_ctorMode mode, bool is_paused);

/** @brief Set the priorities for files in a torrent */
void tr_ctorSetFilePriorities(tr_ctor* ctor, tr_file_index_t const* files, tr_file_index_t n_files, tr_priority_t priority);

/** @brief Set the download flag for files in a torrent */
void tr_ctorSetFilesWanted(tr_ctor* ctor, tr_file_index_t const* files, tr_file_index_t n_files, bool wanted);

/** @brief Get the torrent file that this ctor's metainfo came from,
           or empty if `tr_ctorSetMetainfoFromFile()` wasn't used */
std::optional<std::string> tr_ctorGetSourceFile(tr_ctor const* ctor);

/**
 * Instantiate a single torrent.
 *
 * Returns a pointer to the torrent on success, or nullptr on failure.
 *
 * @param ctor               the builder struct
 * @param setme_duplicate_of If the torrent couldn't be created because it's a duplicate,
 *                           this is set to point to the original torrent.
 */
tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of);

/** @} */

// --- Torrents

/** @addtogroup tr_torrent Torrents
    @{ */

using tr_torrent_remove_func = std::function<bool(std::string_view filename, tr_error* error)>;

/**
 * @brief Removes our torrent and .resume files for this torrent
 * @param remove_func A function that deletes a file.
 *                    The default is `tr_sys_path_remove()`
 *                    Clients can use this arg to pass in platform-specific code e.g.
 *                    to move to a recycle bin instead of deleting.
 *                    The callback is invoked in the session thread and the filename view
 *                    is only valid for the duration of the call.
 */
void tr_torrentRemove(tr_torrent* tor, bool delete_flag, tr_torrent_remove_func remove_func = {});

/** @brief Start a torrent */
void tr_torrentStart(tr_torrent* torrent);

/** @brief Stop (pause) a torrent */
void tr_torrentStop(tr_torrent* torrent);

using tr_torrent_rename_done_func = std::function<
    void(tr_torrent* torrent, char const* oldpath, char const* newname, int error, void* user_data)>;

/**
 * @brief Rename a file or directory in a torrent.
 *
 * @param tor           the torrent whose path will be renamed
 * @param oldpath       the path to the file or folder that will be renamed
 * @param newname       the file or folder's new name
 * @param callback      the callback invoked when the renaming finishes, or nullptr
 * @param callback_user_data the pointer to pass in the callback's user_data arg
 *
 * As a special case, renaming the root file in a torrent will also
 * update tr_torrentName().
 *
 * EXAMPLES
 *
 *   Consider a tr_torrent where its
 *   tr_torrentFile(tor, 0).name is "frobnitz-linux/checksum" and
 *   tr_torrentFile(tor, 1).name is "frobnitz-linux/frobnitz.iso".
 *
 *   1. tr_torrentRenamePath(tor, "frobnitz-linux", "foo") will rename
 *      the "frotbnitz-linux" folder as "foo", and update both
 *      tr_torrentName(tor) and tr_torrentFile(tor, *).name.
 *
 *   2. tr_torrentRenamePath(tor, "frobnitz-linux/checksum", "foo") will
 *      rename the "frobnitz-linux/checksum" file as "foo" and update
 *      files[0].name to "frobnitz-linux/foo".
 *
 * RETURN
 *
 *   Changing the torrent's internal fields requires a session thread lock,
 *   so this function returns asynchronously to avoid blocking. If you don't
 *   want to be notified when the function has finished, you can pass nullptr
 *   as the callback arg.
 *
 *   On success, the callback's error argument will be 0.
 *
 *   If oldpath can't be found in files[*].name, or if newname is already
 *   in files[*].name, or contains a directory separator, or is nullptr, "",
 *   ".", or "..", the error argument will be EINVAL.
 *
 *   If the path exists on disk but can't be renamed, the error argument
 *   will be the errno set by rename().
 */
void tr_torrentRenamePath(
    tr_torrent* tor,
    std::string_view oldpath,
    std::string_view newname,
    tr_torrent_rename_done_func callback,
    void* callback_user_data);

enum : uint8_t
{
    TR_LOC_MOVING,
    TR_LOC_DONE,
    TR_LOC_ERROR
};

/**
 * @brief Tell transmission where to find this torrent's local data.
 *
 * if `move_from_old_path` is `true`, the torrent's incompleteDir
 * will be clobbered s.t. additional files being added will be saved
 * to the torrent's downloadDir.
 */
void tr_torrentSetLocation(tr_torrent* torrent, char const* location, bool move_from_old_path, int volatile* setme_state);

uint64_t tr_torrentGetBytesLeftToAllocate(tr_torrent const* torrent);

/**
 * @brief Returns this torrent's unique ID.
 *
 * IDs are fast lookup keys, but are not persistent between sessions.
 * If you need that, use `tr_torrentView().hash_string`.
 */
tr_torrent_id_t tr_torrentId(tr_torrent const* torrent);

tr_torrent* tr_torrentFindFromId(tr_session* session, tr_torrent_id_t id);

tr_torrent* tr_torrentFindFromMetainfo(tr_session* session, tr_torrent_metainfo const* metainfo);

[[nodiscard]] tr_torrent* tr_torrentFindFromMagnetLink(tr_session* session, std::string_view magnet_link);

/**
 * @brief Set metainfo if possible.
 * @return True if given metainfo was set.
 *
 */
bool tr_torrentSetMetainfoFromFile(tr_torrent* torrent, tr_torrent_metainfo const* metainfo, char const* filename);

/**
 * @return this torrent's name.
 */
[[nodiscard]] std::string tr_torrentName(tr_torrent const* tor);

/**
 * @brief find the location of a torrent's file by looking with and without
 *        the ".part" suffix, looking in downloadDir and incompleteDir, etc.
 * @return the path of this file, or an empty string if no file exists yet.
 * @param tor the torrent whose file we're looking for
 * @param file_num the fileIndex, in [0...tr_torrentFileCount())
 */
[[nodiscard]] std::string tr_torrentFindFile(tr_torrent const* tor, tr_file_index_t file_num);

// --- Torrent speed limits

size_t tr_torrentGetSpeedLimit_KBps(tr_torrent const* tor, tr_direction dir);
void tr_torrentSetSpeedLimit_KBps(tr_torrent* tor, tr_direction dir, size_t limit_kbyps);

bool tr_torrentUsesSpeedLimit(tr_torrent const* tor, tr_direction dir);
void tr_torrentUseSpeedLimit(tr_torrent* tor, tr_direction dir, bool enabled);

bool tr_torrentUsesSessionLimits(tr_torrent const* tor);
void tr_torrentUseSessionLimits(tr_torrent* tor, bool enabled);

// --- Ratio Limits

enum tr_ratiolimit : uint8_t
{
    /* follow the global settings */
    TR_RATIOLIMIT_GLOBAL = 0,
    /* override the global settings, seeding until a certain ratio */
    TR_RATIOLIMIT_SINGLE = 1,
    /* override the global settings, seeding regardless of ratio */
    TR_RATIOLIMIT_UNLIMITED = 2
};

tr_ratiolimit tr_torrentGetRatioMode(tr_torrent const* tor);
void tr_torrentSetRatioMode(tr_torrent* tor, tr_ratiolimit mode);

double tr_torrentGetRatioLimit(tr_torrent const* tor);
void tr_torrentSetRatioLimit(tr_torrent* tor, double desired_ratio);

bool tr_torrentGetSeedRatio(tr_torrent const* tor, double* ratio);

// --- Idle Time Limits

enum tr_idlelimit : uint8_t
{
    /* follow the global settings */
    TR_IDLELIMIT_GLOBAL = 0,
    /* override the global settings, seeding until a certain idle time */
    TR_IDLELIMIT_SINGLE = 1,
    /* override the global settings, seeding regardless of activity */
    TR_IDLELIMIT_UNLIMITED = 2
};

tr_idlelimit tr_torrentGetIdleMode(tr_torrent const* tor);
void tr_torrentSetIdleMode(tr_torrent* tor, tr_idlelimit mode);

uint16_t tr_torrentGetIdleLimit(tr_torrent const* tor);
void tr_torrentSetIdleLimit(tr_torrent* tor, uint16_t idle_minutes);

// --- Peer Limits

uint16_t tr_torrentGetPeerLimit(tr_torrent const* tor);
void tr_torrentSetPeerLimit(tr_torrent* tor, uint16_t max_connected_peers);

// --- File Priorities

/**
 * @brief Set a batch of files to a particular priority.
 *
 * @param priority must be one of TR_PRI_NORMAL, _HIGH, or _LOW
 */
void tr_torrentSetFilePriorities(
    tr_torrent* torrent,
    tr_file_index_t const* files,
    tr_file_index_t file_count,
    tr_priority_t priority);

/** @brief Set a batch of files to be downloaded or not. */
void tr_torrentSetFileDLs(tr_torrent* torrent, tr_file_index_t const* files, tr_file_index_t n_files, bool wanted);

/**
 * Returns a permanently interned string of the torrent's download directory.
 */
[[nodiscard]] std::string_view tr_torrentGetDownloadDir(tr_torrent const* torrent);

/* Raw function to change the torrent's downloadDir field.
   This should only be used by libtransmission or to bootstrap
   a newly-instantiated tr_torrent object. */
void tr_torrentSetDownloadDir(tr_torrent* torrent, std::string_view path);

/**
 * Returns a permanently interned string of the torrent's root directory.
 *
 * This will usually be the downloadDir. However if the torrent
 * has an incompleteDir enabled and hasn't finished downloading
 * yet, that will be returned instead.
 */
[[nodiscard]] std::string_view tr_torrentGetCurrentDir(tr_torrent const* tor);

/**
 * Returns a the magnet link to the torrent.
 */
[[nodiscard]] std::string tr_torrentGetMagnetLink(tr_torrent const* tor);

// ---

/**
 * Returns a string listing its tracker's announce URLs.
 * One URL per line, with a blank line between tiers.
 *
 * NOTE: this only includes the trackers included in the torrent and,
 * along with `tr_torrentSetTrackerList()`, is intended for import/export
 * and user editing. It does *not* include the "default trackers" that
 * are applied to all public torrents. If you want a full display of all
 * trackers, use `tr_torrentTracker()` and `tr_torrentTrackerCount()`
 */
[[nodiscard]] std::string tr_torrentGetTrackerList(tr_torrent const* tor);

/**
 * Sets a torrent's tracker list from a list of announce URLs with one
 * URL per line and a blank line between tiers.
 *
 * This updates both the `torrent` object's tracker list
 * and the metainfo file in `tr_sessionGetConfigDir()`'s torrent subdirectory.
 */
bool tr_torrentSetTrackerList(tr_torrent* tor, std::string_view txt);

// ---

enum tr_completeness : uint8_t
{
    TR_LEECH, /* doesn't have all the desired pieces */
    TR_SEED, /* has the entire torrent */
    TR_PARTIAL_SEED /* has the desired pieces, but not the entire torrent */
};

/**
 * @param was_running whether or not the torrent was running when
 *                    it changed its completeness state
 */
using tr_torrent_completeness_func = void (*)( //
    tr_torrent* torrent,
    tr_completeness completeness,
    bool was_running,
    void* user_data);

using tr_session_ratio_limit_hit_func = void (*)(tr_session*, tr_torrent* torrent, void* user_data);

using tr_session_idle_limit_hit_func = void (*)(tr_session*, tr_torrent* torrent, void* user_data);

/**
 * Register to be notified whenever a torrent's "completeness"
 * changes. This will be called, for example, when a torrent
 * finishes downloading and changes from `TR_LEECH` to
 * either `TR_SEED` or `TR_PARTIAL_SEED`.
 *
 * callback is invoked FROM LIBTRANSMISSION'S THREAD!
 * This means callback must be fast (to avoid blocking peers),
 * shouldn't call libtransmission functions (to avoid deadlock),
 * and shouldn't modify client-level memory without using a mutex!
 *
 * @see `tr_completeness`
 */
void tr_sessionSetCompletenessCallback(tr_session* session, tr_torrent_completeness_func callback, void* user_data);

using tr_session_metadata_func = void (*)(tr_session* session, tr_torrent* torrent, void* user_data);

/**
 * Register to be notified whenever a torrent changes from
 * having incomplete metadata to having complete metadata.
 * This happens when a magnet link finishes downloading
 * metadata from its peers.
 */
void tr_sessionSetMetadataCallback(tr_session* session, tr_session_metadata_func callback, void* user_data);

/**
 * Register to be notified whenever a torrent's ratio limit
 * has been hit. This will be called when the torrent's
 * ul/dl ratio has met or exceeded the designated ratio limit.
 *
 * Has the same restrictions as `tr_sessionSetCompletenessCallback`
 */
void tr_sessionSetRatioLimitHitCallback(tr_session* session, tr_session_ratio_limit_hit_func callback, void* user_data);

/**
 * Register to be notified whenever a torrent's idle limit
 * has been hit. This will be called when the seeding torrent's
 * idle time has met or exceeded the designated idle limit.
 *
 * Has the same restrictions as `tr_sessionSetCompletenessCallback`
 */
void tr_sessionSetIdleLimitHitCallback(tr_session* session, tr_session_idle_limit_hit_func callback, void* user_data);

/**
 * MANUAL ANNOUNCE
 *
 * Trackers usually set an announce interval of 15 or 30 minutes.
 * Users can send one-time announce requests that override this
 * interval by calling `tr_torrentManualUpdate()`.
 *
 * The wait interval for `tr_torrentManualUpdate()` is much smaller.
 * You can test whether or not a manual update is possible
 * (for example, to desensitize the button) by calling
 * `tr_torrentCanManualUpdate()`.
 */

void tr_torrentManualUpdate(tr_torrent* torrent);

bool tr_torrentCanManualUpdate(tr_torrent const* torrent);

// --- tr_peer_stat

struct tr_peer_stat
{
    std::string addr;
    std::string flag_str;

    // The user agent, e.g. `BitTorrent 7.9.1`.
    // Will be an empty string if the agent cannot be determined.
    std::string user_agent;

    tr::Values::Speed rate_to_peer;
    tr::Values::Speed rate_to_client;

    // how many requests the peer has made that we haven't responded to yet
    size_t active_reqs_to_client = {};

    // how many requests we've made and are currently awaiting a response for
    size_t active_reqs_to_peer = {};

    size_t bytes_to_peer = {};
    size_t bytes_to_client = {};

    tr_peer_id_t peer_id = {};

    float progress = {};

    // THESE NEXT FOUR FIELDS ARE EXPERIMENTAL.
    // Don't rely on them; they'll probably go away
    // how many blocks we've sent to this peer in the last 120 seconds
    uint32_t blocks_to_peer = {};
    // how many blocks this client's sent to us in the last 120 seconds
    uint32_t blocks_to_client = {};
    // how many requests to this peer that we've cancelled in the last 120 seconds
    uint32_t cancels_to_peer = {};
    // how many requests this peer made of us, then cancelled, in the last 120 seconds
    uint32_t cancels_to_client = {};

    uint16_t port = {};
    uint8_t from = {};

    bool client_is_choked = {};
    bool client_is_interested = {};
    bool is_downloading_from = {};
    bool is_encrypted = {};
    bool is_incoming = {};
    bool is_seed = {};
    bool is_uploading_to = {};
    bool is_utp = {};
    bool peer_is_choked = {};
    bool peer_is_interested = {};
};

std::vector<tr_peer_stat> tr_torrentPeers(tr_torrent const* torrent);

// --- tr_tracker_stat

enum tr_tracker_state : uint8_t
{
    /* we won't (announce,scrape) this torrent to this tracker because
     * the torrent is stopped, or because of an error, or whatever */
    TR_TRACKER_INACTIVE = 0,
    /* we will (announce,scrape) this torrent to this tracker, and are
     * waiting for enough time to pass to satisfy the tracker's interval */
    TR_TRACKER_WAITING = 1,
    /* it's time to (announce,scrape) this torrent, and we're waiting on a
     * free slot to open up in the announce manager */
    TR_TRACKER_QUEUED = 2,
    /* we're (announcing,scraping) this torrent right now */
    TR_TRACKER_ACTIVE = 3
};

// NOLINTBEGIN(modernize-avoid-c-arrays)
/*
 * Unlike other _view structs, it is safe to keep a tr_tracker_view copy.
 * The announce and scrape strings are interned & never go out-of-scope.
 */
struct tr_tracker_view
{
    char const* announce; // full announce URL
    char const* scrape; // full scrape URL
    char host_and_port[72]; // uniquely-identifying tracker name (`${host}:${port}`)

    // The tracker site's name. Uses the first label before the public suffix
    // (https://publicsuffix.org/) in the announce URL's host.
    // e.g. "https://www.example.co.uk/announce/"'s sitename is "example"
    // RFC 1034 says labels must be less than 64 chars
    char sitename[64];

    char lastAnnounceResult[128]; // if hasAnnounced, the human-readable result of latest announce
    char lastScrapeResult[128]; // if hasScraped, the human-readable result of the latest scrape

    time_t lastAnnounceStartTime; // if hasAnnounced, when the latest announce request was sent
    time_t lastAnnounceTime; // if hasAnnounced, when the latest announce reply was received
    time_t nextAnnounceTime; // if announceState == TR_TRACKER_WAITING, time of next announce

    time_t lastScrapeStartTime; // if hasScraped, when the latest scrape request was sent
    time_t lastScrapeTime; // if hasScraped, when the latest scrape reply was received
    time_t nextScrapeTime; // if scrapeState == TR_TRACKER_WAITING, time of next scrape

    int downloadCount; // number of times this torrent's been downloaded, or -1 if unknown
    int lastAnnouncePeerCount; // if hasAnnounced, the number of peers the tracker gave us
    int leecherCount; // number of leechers the tracker knows of, or -1 if unknown
    int seederCount; // number of seeders the tracker knows of, or -1 if unknown
    int downloader_count; // number of downloaders (BEP-21) the tracker knows of, or -1 if unknown

    size_t tier; // which tier this tracker is in
    tr_tracker_id_t id; // unique transmission-generated ID for use in libtransmission API

    tr_tracker_state announceState; // whether we're announcing, waiting to announce, etc.
    tr_tracker_state scrapeState; // whether we're scraping, waiting to scrape, etc.

    bool hasAnnounced; // true iff we've announced to this tracker during this session
    bool hasScraped; // true iff we've scraped this tracker during this session
    bool isBackup; // only one tracker per tier is used; the others are kept as backups
    bool lastAnnounceSucceeded; // if hasAnnounced, whether or not the latest announce succeeded
    bool lastAnnounceTimedOut; // true iff the latest announce request timed out
    bool lastScrapeSucceeded; // if hasScraped, whether or not the latest scrape succeeded
    bool lastScrapeTimedOut; // true iff the latest scrape request timed out
};
// NOLINTEND(modernize-avoid-c-arrays)

struct tr_tracker_view tr_torrentTracker(tr_torrent const* torrent, size_t i);

/**
 * Count all the trackers (both active and backup) this torrent is using.
 *
 * NOTE: this is for a status display only and may include trackers from
 * the default tracker list if this is a public torrent. If you want a
 * list of trackers the  user can edit, see `tr_torrentGetTrackerList()`.
 */
size_t tr_torrentTrackerCount(tr_torrent const* torrent);

/*
 * This view structure is intended for short-term use. Its pointers are owned
 * by the torrent and may be invalidated if the torrent is edited or removed.
 */
struct tr_file_view
{
    char const* name; // This file's name. Includes the full subpath in the torrent.
    uint64_t have; // the current size of the file, i.e. how much we've downloaded
    uint64_t length; // the total size of the file
    double progress; // have / length
    tr_piece_index_t beginPiece; // piece index where this file starts
    tr_piece_index_t endPiece; // piece index where this file ends (exclusive)
    tr_priority_t priority; // the file's priority
    bool wanted; // do we want to download this file?
};
tr_file_view tr_torrentFile(tr_torrent const* torrent, tr_file_index_t file);

size_t tr_torrentFileCount(tr_torrent const* torrent);

/*
 * This view structure is intended for short-term use. Its pointers are owned
 * by the torrent and may be invalidated if the torrent is edited or removed.
 */
struct tr_webseed_view
{
    char const* url; // the url to download from
    bool is_downloading; // can be true even if speed is 0, e.g. slow download
    uint64_t download_bytes_per_second; // current download speed
};

struct tr_webseed_view tr_torrentWebseed(tr_torrent const* torrent, size_t nth);

size_t tr_torrentWebseedCount(tr_torrent const* torrent);

/*
 * This view structure is intended for short-term use. Its pointers are owned
 * by the torrent and may be invalidated if the torrent is edited or removed.
 */
struct tr_torrent_view
{
    char const* name;
    char const* hash_string;

    char const* comment; // optional; may be nullptr
    char const* creator; // optional; may be nullptr
    char const* source; // optional; may be nullptr

    uint64_t total_size; // total size of the torrent, in bytes

    time_t date_created;

    uint32_t piece_size;
    tr_piece_index_t n_pieces;

    bool is_private;
    bool is_folder;
};

struct tr_torrent_view tr_torrentView(tr_torrent const* tor);

/*
 * Get the filename of Transmission's internal copy of the torrent file.
 */
[[nodiscard]] std::string tr_torrentFilename(tr_torrent const* tor);

/**
 * Use this to draw an advanced progress bar which is 'size' pixels
 * wide. Fills 'tab' which you must have allocated: each byte is set
 * to either -1 if we have the piece, otherwise it is set to the number
 * of connected peers who have the piece.
 */
void tr_torrentAvailability(tr_torrent const* torrent, int8_t* tab, int size);

void tr_torrentAmountFinished(tr_torrent const* torrent, float* tab, int n_tabs);

/**
 * Queue a torrent for verification.
 */
void tr_torrentVerify(tr_torrent* torrent);

bool tr_torrentHasMetadata(tr_torrent const* tor);

/**
 * What the torrent is doing right now.
 *
 * Note: these values will become a straight enum at some point in the future.
 * Do not rely on their current `bitfield` implementation
 */
enum tr_torrent_activity : uint8_t
{
    TR_STATUS_STOPPED = 0, /* Torrent is stopped */
    TR_STATUS_CHECK_WAIT = 1, /* Queued to check files */
    TR_STATUS_CHECK = 2, /* Checking files */
    TR_STATUS_DOWNLOAD_WAIT = 3, /* Queued to download */
    TR_STATUS_DOWNLOAD = 4, /* Downloading */
    TR_STATUS_SEED_WAIT = 5, /* Queued to seed */
    TR_STATUS_SEED = 6 /* Seeding */
};
enum tr_peer_from : uint8_t
{
    TR_PEER_FROM_INCOMING = 0, /* connections made to the listening port */
    TR_PEER_FROM_LPD, /* peers found by local announcements */
    TR_PEER_FROM_TRACKER, /* peers found from a tracker */
    TR_PEER_FROM_DHT, /* peers found from the DHT */
    TR_PEER_FROM_PEX, /* peers found from PEX */
    TR_PEER_FROM_RESUME, /* peers found in the .resume file */
    TR_PEER_FROM_LTEP, /* peer address provided in an LTEP handshake */
    TR_PEER_FROM_N_TYPES
};
enum tr_eta : time_t // NOLINT(performance-enum-size)
{
    TR_ETA_NOT_AVAIL = -1,
    TR_ETA_UNKNOWN = -2,
};

/** @brief Used by `tr_torrentStat()` to tell clients about a torrent's state and statistics */
struct tr_stat
{
    /** A warning or error message regarding the torrent.
        @see error */
    std::string error_string;

    /** Byte count of all the piece data we'll have downloaded when we're done,
        whether or not we have it yet. If we only want some of the files,
        this may be less than `tr_torrent_view.total_size`.
        [0...tr_torrent_view.total_size] */
    uint64_t size_when_done = {};

    /** Byte count of how much data is left to be downloaded until we've got
        all the pieces that we want. [0...tr_stat.sizeWhenDone] */
    uint64_t left_until_done = {};

    /** Byte count of all the piece data we want and don't have yet,
        but that a connected peer does have. [0...leftUntilDone] */
    uint64_t desired_available = {};

    /** Byte count of all the corrupt data you've ever downloaded for
        this torrent. If you're on a poisoned torrent, this number can
        grow very large. */
    uint64_t corrupt_ever = {};

    /** Byte count of all data you've ever uploaded for this torrent. */
    uint64_t uploaded_ever = {};

    /** Byte count of all the non-corrupt data you've ever downloaded
        for this torrent. If you deleted the files and downloaded a second
        time, this will be `2*totalSize`.. */
    uint64_t downloaded_ever = {};

    /** Byte count of all the checksum-verified data we have for this torrent.
      */
    uint64_t have_valid = {};

    /** Byte count of all the partial piece data we have for this torrent.
        As pieces become complete, this value may decrease as portions of it
        are moved to `corrupt` or `haveValid`. */
    uint64_t have_unchecked = {};

    // Speed of all piece being sent for this torrent.
    // This ONLY counts piece data.
    tr::Values::Speed piece_upload_speed;

    // Speed of all piece being received for this torrent.
    // This ONLY counts piece data.
    tr::Values::Speed piece_download_speed;

    // When the torrent was first added
    time_t added_date = {};

    // When the torrent finished downloading
    time_t done_date = {};

    // When the torrent was last started
    time_t start_date = {};

    // The last time we uploaded or downloaded piece data on this torrent
    time_t activity_date = {};

    // The last time during this session that a rarely-changing field
    // changed -- e.g. any `tr_torrent_metainfo` field (trackers, filenames, name)
    // or download directory. RPC clients can monitor this to know when
    // to reload fields that rarely change.
    time_t edit_date = {};

    // Number of seconds since the last activity (or since started).
    // -1 if activity is not seeding or downloading.
    time_t idle_secs = {};

    // Cumulative seconds the torrent's ever spent downloading
    time_t seconds_downloading = {};

    // Cumulative seconds the torrent's ever spent seeding
    time_t seconds_seeding = {};

    // If downloading, estimated number of seconds left until the torrent is done.
    // If seeding, estimated number of seconds left until seed ratio is reached.
    time_t eta = {};

    // If seeding, number of seconds left until the idle time limit is reached.
    time_t eta_idle = {};

    // This torrent's queue position.
    // All torrents have a queue position, even if it's not queued.
    size_t queue_position = {};

    // When `tr_stat.activity` is `TR_STATUS_CHECK` or `TR_STATUS_CHECK_WAIT`,
    // this is the percentage of how much of the files has been
    // verified. When it gets to 1, the verify process is done.
    // Range is [0..1]
    // @see `tr_stat.activity`
    float recheck_progress = {};

    // How much has been downloaded of the entire torrent.
    // Range is [0..1]
    float percent_complete = {};

    // How much of the metadata the torrent has.
    // For torrents added from a torrent this will always be 1.
    // For magnet links, this number will from from 0 to 1 as the metadata is downloaded.
    // Range is [0..1]
    float metadata_percent_complete = {};

    // How much has been downloaded of the files the user wants. This differs
    // from percentComplete if the user wants only some of the torrent's files.
    // Range is [0..1]
    // @see tr_stat.left_until_done
    float percent_done = {};

    // How much has been uploaded to satisfy the seed ratio.
    // This is 1 if the ratio is reached or the torrent is set to seed forever.
    // Range is [0..1] */
    float seed_ratio_percent_done = {};

    // Total uploaded bytes / size_when_done.
    // NB: In Transmission 3.00 and earlier, this was total upload / download,
    // which caused edge cases when total download was less than size_when_done.
    float upload_ratio = {};

    // The torrent's unique Id.
    // @see `tr_torrentId()`
    tr_torrent_id_t id = {};

    // Number of peers that we're connected to
    uint16_t peers_connected = {};

    // How many connected peers we found out about from the tracker, or from pex,
    // or from incoming connections, or from our resume file.
    std::array<uint16_t, TR_PEER_FROM_N_TYPES> peers_from = {};

    // How many known peers we found out about from the tracker, or from pex,
    // or from incoming connections, or from our resume file.
    std::array<uint16_t, TR_PEER_FROM_N_TYPES> known_peers_from = {};

    // Number of peers that are sending data to us.
    uint16_t peers_sending_to_us = {};

    // Number of peers that we're sending data to
    uint16_t peers_getting_from_us = {};

    // Number of webseeds that are sending data to us.
    uint16_t webseeds_sending_to_us = {};

    // What is this torrent doing right now?
    tr_torrent_activity activity = {};

    enum class Error : uint8_t
    {
        Ok, // everything's fine
        TrackerWarning, // tracker returned a warning
        TrackerError, // tracker returned an error
        LocalError // local non-tracker error, e.g. disk full or file permissions
    };

    // Defines what kind of text is in error_string.
    // @see errorString
    Error error = Error::Ok;

    // A torrent is considered finished if it has met its seed ratio.
    // As a result, only paused torrents can be finished.
    bool finished = {};

    // True if the torrent is running, but has been idle for long enough
    // to be considered stalled.  @see `tr_sessionGetQueueStalledMinutes()`
    bool is_stalled = {};
};

// Return a `tr_stat` structure with updated information on the torrent.
// This is typically called by the GUI clients every
// second or so to get a new snapshot of the torrent's status.
tr_stat tr_torrentStat(tr_torrent* torrent);

// Batch version of tr_torrentStat().
// Prefer calling this over calling the single-torrent version in a loop.
// TODO(c++20) take a std::span argument
std::vector<tr_stat> tr_torrentStat(tr_torrent* const* torrents, size_t n_torrents);
