/*
 * voicecat.h — the C ABI for libvoicecat.
 *
 * This is the single boundary every front-end calls: Swift (macOS/iOS) and C# (Windows)
 * both bind to this header, and the server links the same core. It is C-linkage and
 * handle-based so it is stable and trivially bindable from any language.
 *
 * Design: docs/architecture.md §4. Everything here is async + event-driven — calls return
 * immediately and results/state changes arrive via the vc_callbacks.on_event callback.
 *
 * STATUS: real, behind VOICECAT_HAS_NET (the `dev`/`release`/`server-release` presets —
 * vcpkg deps on; see docs/building.md). As of M3, control plane, voice, multi-stream, device
 * enumeration, VAD/PTT, and stereo playback all work for real via core/src/core/client.cpp.
 * The no-deps `skeleton` preset still links a stub vc_client that returns VC_ERR_NOT_IMPLEMENTED
 * for everything below `connect`, purely to keep that skeleton build green. webrtc AEC/NS/AGC
 * remains an inert passthrough regardless of preset (no Windows/MSVC port upstream —
 * docs/voice.md §8/§11, PROGRESS.md).
 */
#ifndef VOICECAT_H
#define VOICECAT_H

#include <stddef.h>
#include <stdint.h>

#if defined(__cplusplus)
extern "C" {
#endif

/* ── Export macro ─────────────────────────────────────────────────────────── */
#if defined(VOICECAT_STATIC)
#define VC_API
#elif defined(_WIN32)
#if defined(VOICECAT_BUILDING)
#define VC_API __declspec(dllexport)
#else
#define VC_API __declspec(dllimport)
#endif
#else
#if defined(VOICECAT_BUILDING)
#define VC_API __attribute__((visibility("default")))
#else
#define VC_API
#endif
#endif

/* ── Version ──────────────────────────────────────────────────────────────── */
#define VOICECAT_VERSION_MAJOR 0
#define VOICECAT_VERSION_MINOR 0
#define VOICECAT_VERSION_PATCH 2  /* +vc_set_mixed_output_sink / vc_set_external_playback (iOS VPIO) */

/* The control-protocol version this build speaks (docs/protocol.md §4).
 * v2 widened the UDP voice frame seq field u16 → u64 (docs/voice.md §2); a v2 server
 * and a v1 client cannot interoperate, so the Hello handshake rejects on mismatch. */
#define VOICECAT_PROTOCOL_VERSION 2

/* ── Result codes ─────────────────────────────────────────────────────────── */
typedef enum vc_result {
    VC_OK = 0,
    VC_ERR_NOT_IMPLEMENTED = 1, /* skeleton stub */
    VC_ERR_INVALID_ARG = 2,
    VC_ERR_NOT_CONNECTED = 3,
    VC_ERR_ALREADY = 4,
    VC_ERR_AUTH_FAILED = 5,
    VC_ERR_PERMISSION_DENIED = 6,
    VC_ERR_TIMEOUT = 7,
    VC_ERR_IO = 8,
    VC_ERR_PROTOCOL = 9,
    VC_ERR_CRYPTO = 10,
    VC_ERR_AUDIO = 11,
    VC_ERR_INTERNAL = 12,
} vc_result;

typedef enum vc_log_level {
    VC_LOG_TRACE = 0,
    VC_LOG_DEBUG = 1,
    VC_LOG_INFO = 2,
    VC_LOG_WARN = 3,
    VC_LOG_ERROR = 4,
    VC_LOG_OFF = 5,
} vc_log_level;

typedef enum vc_connection_state {
    VC_STATE_DISCONNECTED = 0,
    VC_STATE_CONNECTING = 1,
    VC_STATE_TLS_HANDSHAKE = 2,
    VC_STATE_AUTHENTICATING = 3,
    VC_STATE_CONNECTED = 4,
    /* M4: between TLS_HANDSHAKE and AUTHENTICATING — the handshake succeeded and the core is
     * waiting for vc_confirm_server_identity() (see VC_EVENT_SERVER_IDENTITY below). Appended
     * at the end (not inserted) to keep existing enum values stable — additive-only ABI. */
    VC_STATE_VERIFYING_IDENTITY = 5,
} vc_connection_state;

typedef enum vc_text_scope {
    VC_TEXT_CHANNEL = 0,
    VC_TEXT_PRIVATE = 1,
    VC_TEXT_SERVER = 2,
} vc_text_scope;

typedef enum vc_device_kind {
    VC_DEVICE_INPUT = 0,
    VC_DEVICE_OUTPUT = 1,
} vc_device_kind;

typedef enum vc_stream_kind {
    VC_STREAM_MIC = 0,
    VC_STREAM_SCREEN_AUDIO = 1, /* system/desktop audio (docs/voice.md §9) */
    VC_STREAM_AUX_DEVICE = 2,
} vc_stream_kind;

/* Send-side input gate (docs/voice.md §11). */
typedef enum vc_input_mode {
    VC_INPUT_VOICE_ACTIVATION = 0,
    VC_INPUT_PUSH_TO_TALK = 1,
    /* Transmit unconditionally — no VAD gate. Added at the end to keep existing values stable. */
    VC_INPUT_ALWAYS_ON = 2,
} vc_input_mode;

typedef enum vc_event_type {
    VC_EVENT_CONNECTION_STATE = 0, /* connection_state set */
    VC_EVENT_AUTH_RESULT = 1,      /* result set; user_id = self on success */
    VC_EVENT_CHANNEL_LIST = 2,     /* channel tree snapshot/delta available */
    VC_EVENT_USER_JOINED = 3,      /* user_id, channel_id, text = nickname */
    VC_EVENT_USER_LEFT = 4,        /* user_id */
    VC_EVENT_USER_UPDATED = 5,     /* user_id */
    VC_EVENT_TEXT_MESSAGE = 6,     /* text_scope, user_id (sender), channel_id, text */
    VC_EVENT_STREAM_STARTED = 7,   /* user_id, stream_id */
    VC_EVENT_STREAM_STOPPED = 8,   /* user_id, stream_id */
    VC_EVENT_TALK_STATE = 9,       /* user_id, stream_id, u32a = talking(0/1) */
    VC_EVENT_ERROR = 10,           /* result, text */
    VC_EVENT_DISCONNECTED = 11,    /* result, text = reason */
    /* M4 additions — appended, not inserted, to keep existing enum values stable. */
    VC_EVENT_JOIN_RESULT = 12,     /* result (VC_OK/VC_ERR_*), channel_id, text = error on
                                       failure. Reply to vc_join_channel(). */
    VC_EVENT_SERVER_IDENTITY = 13, /* u32a = vc_tofu_status, text = hex-encoded TLS leaf-cert
                                       SHA-256 fingerprint (the value being pinned — see
                                       vc_confirm_server_identity). Emitted once per connect
                                       attempt, right after the TLS handshake succeeds. The
                                       connection is held open until vc_confirm_server_identity()
                                       is called. */
    /* M5 additions — appended, not inserted. */
    VC_EVENT_GENERIC_RESULT = 14,  /* result, u32a = server error code, text = message. Reply
                                       to vc_kick_user/vc_ban_user/vc_set_permission/
                                       vc_move_user/vc_create_channel/vc_edit_channel/
                                       vc_delete_channel/vc_create_account/vc_reset_password/
                                       vc_delete_account. */
    VC_EVENT_ACCOUNT_LIST = 15,    /* Reply to vc_list_accounts. */
} vc_event_type;

/* TOFU server-identity classification (M4) — see VC_EVENT_SERVER_IDENTITY and
 * vc_confirm_server_identity. Pins the TLS leaf certificate's own SHA-256 fingerprint
 * (verifiable directly from the handshake), NOT the declared Ed25519
 * server_identity_fingerprint from ServerHello — the TLS cert and the server's Ed25519
 * identity key are generated independently with no cryptographic binding between them today
 * (docs/security.md §1.1), so pinning the self-declared value would be circular. The Ed25519
 * fingerprint is still available for human-readable display via
 * vc_get_server_identity_display(), it just isn't the value this gate accepts/rejects on. */
typedef enum vc_tofu_status {
    VC_TOFU_FIRST_CONNECT = 0, /* no pin on file yet for this host:port */
    VC_TOFU_MATCHED       = 1, /* matches the previously pinned fingerprint */
    VC_TOFU_MISMATCH      = 2, /* DIFFERENT from the pinned fingerprint — possible MITM or a
                                   legitimate server key rotation; warn loudly */
} vc_tofu_status;

/* ── Structs ──────────────────────────────────────────────────────────────── */

/*
 * An event delivered to vc_callbacks.on_event. Pointer fields are owned by the core and
 * valid ONLY for the duration of the callback — copy what you need. Which fields are
 * meaningful depends on `type` (see vc_event_type comments above).
 */
typedef struct vc_event {
    vc_event_type type;
    vc_connection_state connection_state;
    int32_t result; /* vc_result */
    uint32_t user_id;
    uint32_t channel_id;
    uint32_t stream_id;
    vc_text_scope text_scope;
    uint32_t u32a; /* generic small payload, meaning per event type */
    const char* text;
    uint64_t timestamp_unix_ms;
} vc_event;

typedef struct vc_callbacks {
    /* State changes, messages, presence. Called on the core's event thread. */
    void (*on_event)(void* user, const vc_event* ev);
    /* Throttled level meter (RMS 0..1) for a local or remote stream; may be NULL. */
    void (*on_level)(void* user, uint32_t stream_id, float rms);
    void* user;
} vc_callbacks;

typedef struct vc_config {
    const char* client_name;    /* e.g. "VoiceCat-macOS" */
    const char* client_version; /* e.g. "0.0.1" */
    vc_log_level log_level;
    /* M4, optional (added at the end — existing brace-initialized callers default this to
     * NULL, no source change needed). Path to the TOFU pin file (see VC_EVENT_SERVER_IDENTITY/
     * vc_confirm_server_identity). NULL = a built-in relative default
     * ("./voicecat_tofu_pins.txt") so existing tests need no real persistence. A real app
     * (e.g. the Windows client) should pass an explicit per-user path, e.g.
     * "%AppData%\VoiceCat\tofu_pins.txt". */
    const char* tofu_store_path;
} vc_config;

typedef struct vc_stream_desc {
    vc_stream_kind kind;
    const char* device_id; /* NULL = default device for this kind */
    const char* label;     /* human label, e.g. "Microphone" */
    /* If 1, the caller will feed PCM via vc_stream_feed_pcm; the core will NOT start its own
     * WASAPI loopback capture. Only meaningful for VC_STREAM_SCREEN_AUDIO on Windows. Callers
     * that brace-initialize this struct (tests, macOS) get 0 = auto-start loopback — no ABI
     * break. See clients/windows/VoiceCat.App/Audio/ProcessAudioMixer.cs for the Windows
     * per-app capture implementation that sets this. */
    int external_feed;
} vc_stream_desc;

/* The effective Opus configuration in use for a stream — for a stream you own, this is
 * StreamAnnounceResult.effective_audio (channel-enforced, docs/voice.md §3); for a remote
 * stream, it's the peer's broadcast StreamInfo.audio. See vc_get_stream_audio_config. */
typedef struct vc_audio_config {
    uint32_t codec;                /* 0 = OPUS */
    uint32_t mode;                 /* 0 = mono, 1 = stereo */
    uint32_t sample_rate;
    uint32_t bitrate_bps;
    uint32_t frame_ms;
    uint32_t application;          /* 0 = VOIP, 1 = AUDIO, 2 = LOWDELAY */
    int      fec;                  /* bool */
    uint32_t expected_packet_loss; /* % 0..100 */
    int      dtx;                  /* bool */
    uint32_t complexity;           /* 0..10 */
    int      dred;                 /* bool — Deep REDundancy (Opus 1.6), off by default */
} vc_audio_config;

/* M5: permission bitset (mirrors protocol Permissions). */
typedef struct vc_permissions {
    int can_create_temp_channel; /* bool */
    int can_kick;                /* bool */
    int can_ban;                 /* bool */
    int can_move_users;          /* bool */
    int can_admin_accounts;      /* bool */
    int is_admin;                /* bool */
} vc_permissions;

/* M5: account entry (reply to vc_list_accounts / vc_get_account_list). */
typedef struct vc_account {
    const char* username;
    int         is_admin;            /* bool */
    uint64_t    created_at_unix_ms;
    uint64_t    last_login_unix_ms;
} vc_account;

typedef struct vc_account_list {
    vc_account* items;
    size_t      count;
} vc_account_list;

/* M5: channel creation/edition descriptor. */
typedef struct vc_channel_info {
    uint32_t id;                  /* 0 = new channel for create */
    uint32_t parent_id;           /* 0 = root */
    const char* name;
    const char* topic;
    int         password_protected; /* bool */
    const char* password;           /* nullable; ignored if password_protected == 0 */
    uint32_t    max_users;          /* 0 = unlimited */
    uint32_t    sort_order;
    /* Audio config — 0/NULL fields use server defaults. */
    vc_audio_config audio;
} vc_channel_info;

typedef struct vc_device {
    const char* id;
    const char* name;
    int is_default; /* bool */
} vc_device;

typedef struct vc_device_list {
    vc_device* items;
    size_t count;
} vc_device_list;

/* ── Channel / user / stream snapshots (M4 — for the channel-tree/user-list UI) ───────────
 * Pull-based: re-call after VC_EVENT_CHANNEL_LIST / VC_EVENT_USER_JOINED / _LEFT / _UPDATED to
 * refresh — there is no push variant; those events just mean "go look". Same ownership
 * contract as vc_device/vc_device_list above: core-allocated, caller frees with the matching
 * vc_free_*, items' const char* fields are invalid after that call. */
typedef struct vc_channel {
    uint32_t    id;
    uint32_t    parent_id;          /* 0 = root */
    const char* name;
    const char* topic;
    int         password_protected; /* bool */
    uint32_t    max_users;          /* 0 = unlimited */
} vc_channel;

typedef struct vc_channel_list {
    vc_channel* items;
    size_t      count;
} vc_channel_list;

typedef struct vc_user {
    uint32_t    id;
    const char* nickname;
    int         is_guest; /* bool */
    uint32_t    channel_id;
    int         self_mic_muted; /* bool */
    int         self_deafened;  /* bool */
    int         server_muted;   /* bool */
    int         server_deafened; /* bool */
} vc_user;

typedef struct vc_user_list {
    vc_user* items;
    size_t   count;
} vc_user_list;

/* Per-user stream summary — lighter than vc_audio_config; for the full effective Opus config
 * of a specific (user_id, stream_id), use the existing vc_get_stream_audio_config. */
typedef struct vc_stream_summary {
    uint32_t       stream_id;
    vc_stream_kind kind;
    const char*    label;
} vc_stream_summary;

typedef struct vc_stream_summary_list {
    vc_stream_summary* items;
    size_t             count;
} vc_stream_summary_list;

/* Receive-side state the local listener has chosen for a specific remote stream — the
 * counterpart to vc_set_remote_stream, so a UI can reopen its per-mix controls at the
 * listener's actual current settings. All LOCAL (no protocol traffic) — docs/voice.md §10.
 * If (user_id, stream_id) is known but the listener has never called vc_set_remote_stream on
 * it, the defaults are gain=1.0, muted=0, noise_reduction=0 (matching a fresh RemoteStream). */
typedef struct vc_remote_stream_state {
    float gain;              /* 0.0–… ; default 1.0 */
    int   muted;             /* bool */
    int   noise_reduction;   /* bool */
} vc_remote_stream_state;

/* Opaque client handle. */
typedef struct vc_client vc_client;

/* ── Lifecycle ────────────────────────────────────────────────────────────── */
VC_API const char* vc_version_string(void);
VC_API const char* vc_result_string(vc_result code);

VC_API vc_client* vc_client_create(const vc_config* cfg, vc_callbacks cb);
VC_API void vc_client_destroy(vc_client* c);

/* ── Connection & auth (async; results via on_event) ──────────────────────── */
VC_API vc_result vc_connect(vc_client* c, const char* host, uint16_t port);
VC_API vc_result vc_disconnect(vc_client* c);
VC_API vc_result vc_authenticate_guest(vc_client* c, const char* nickname);
VC_API vc_result vc_authenticate_user(vc_client* c, const char* username,
                                      const char* password);

/* ── Channels ─────────────────────────────────────────────────────────────── */
/* Result arrives as VC_EVENT_JOIN_RESULT, not a return value beyond "request queued". `password`
 * is forwarded to the server's JoinChannelRequest.password for channels with
 * vc_channel.password_protected set; NOTE (M4): no in-tree channel currently has a server-side
 * password to check against — channel creation/passwords are a future (M5+) feature, so this
 * path is wired but not yet exercisable end-to-end. */
VC_API vc_result vc_join_channel(vc_client* c, uint32_t channel_id,
                                 const char* password /* nullable */);
VC_API vc_result vc_leave_channel(vc_client* c);

/* ── Local media streams (mic / screen audio / aux) ───────────────────────── */
VC_API vc_result vc_stream_start(vc_client* c, const vc_stream_desc* desc,
                                 uint32_t* out_stream_id);
VC_API vc_result vc_stream_stop(vc_client* c, uint32_t stream_id);
VC_API vc_result vc_set_input_device(vc_client* c, uint32_t stream_id,
                                     const char* device_id);

/* Send-side: input gate mode + PTT key state, and self mute/deafen. */
VC_API vc_result vc_set_input_mode(vc_client* c, vc_input_mode mode);
/* VAD threshold: normalized RMS 0.0–1.0; default ~0.025. Takes effect immediately —
 * recreates the VAD gate if a MIC stream is already active. No-op when mode != VOICE_ACTIVATION
 * (value is remembered and applied if the mode switches back). */
VC_API vc_result vc_set_vad_threshold(vc_client* c, float threshold);
VC_API vc_result vc_set_push_to_talk(vc_client* c, int active /* bool */);
VC_API vc_result vc_set_self_mute(vc_client* c, int mic_muted, int deafened);

/* Global playback volume applied after mixing all remote streams. gain 0.0 = silent,
 * 1.0 = unity (default), >1.0 amplifies. Always LOCAL — no protocol traffic. */
VC_API vc_result vc_set_output_volume(vc_client* c, float gain);

/* Send-side microphone input gain. Applied to captured MIC PCM before the VAD/PTT gate and
 * Opus encode (so boosting a quiet mic also helps it cross the VAD threshold). gain 0.0 = silent,
 * 1.0 = unity (default), >1.0 amplifies; the boosted signal is clamped to int16. MIC stream only;
 * always LOCAL — no protocol traffic. */
VC_API vc_result vc_set_input_gain(vc_client* c, float gain);

/* Send-side microphone noise suppression (RNNoise). Denoises captured MIC PCM before the input
 * gain and the VAD/PTT gate, so everyone hears the cleaned signal (one pass for all listeners).
 * enable != 0 turns it on. MIC stream only, mono only; always LOCAL — no protocol traffic.
 * Independent of the per-listener receive-side NR in vc_set_remote_stream (docs/voice.md §10). */
VC_API vc_result vc_set_input_noise_reduction(vc_client* c, int enable);

/* Receive-side, per remote stream, all LOCAL (no protocol traffic) — docs/voice.md §10:
 * gain (0..) , mute, and listener-chosen noise reduction on a specific user's stream. */
VC_API vc_result vc_set_remote_stream(vc_client* c, uint32_t user_id, uint32_t stream_id,
                                       float gain, int muted, int noise_reduction);

/* Reads back the receive-side state last set on (user_id, stream_id) via
 * vc_set_remote_stream (or the defaults if never set). VC_ERR_INVALID_ARG if the user/stream
 * isn't known. */
VC_API vc_result vc_get_remote_stream(vc_client* c, uint32_t user_id, uint32_t stream_id,
                                      vc_remote_stream_state* out);

/* Effective Opus config in use for (user_id, stream_id) — your own stream or a peer's.
 * VC_ERR_INVALID_ARG if the user/stream isn't known. */
VC_API vc_result vc_get_stream_audio_config(vc_client* c, uint32_t user_id, uint32_t stream_id,
                                            vc_audio_config* out);

/* TEST-ONLY — not for production use. Bypasses the real capture device, injecting raw PCM
 * directly into the named local stream's encode pipeline (see AudioEngine::inject_capture).
 * Exists so automated tests can drive the real vc_client/ABI path end-to-end without a
 * microphone. `stream_id` is the id returned by vc_stream_start. */
VC_API vc_result vc_test_inject_capture(vc_client* c, uint32_t stream_id, const int16_t* pcm,
                                        size_t samples);

/* Set the capture channel count for a local MIC stream (1 = mono, 2 = stereo interleaved).
 * Must be called after vc_stream_start. Stores the value; it takes effect on the next engine
 * (re)start. Does NOT restart the engine itself — the caller must follow up with
 * vc_audio_restart() after AVAudioSession routing has settled (iOS) or after any platform
 * audio-session reconfiguration. On iOS the Swift AVAudioSession routing layer enables stereo
 * built-in mic capture by switching the built-in mic's data source to the .stereo polar
 * pattern (setPreferredDataSource + setPreferredPolarPattern(.stereo) + setPreferredInput +
 * setInputDataSource), calls this to record the desired channel count, and then calls
 * vc_audio_restart() so the core reopens capture and playback against the new route.
 * VC_ERR_INVALID_ARG if stream_id is unknown or channels is not 1 or 2. */
VC_API vc_result vc_set_capture_channels(vc_client* c, uint32_t stream_id, uint32_t channels);

/* ── External PCM feed/tap ─────────────────────────────────────────────────── */

/* External PCM feed — production-grade API for driving a local stream's encode pipeline
 * with caller-supplied PCM instead of (or in addition to) a hardware capture device. The
 * stream must already be started (vc_stream_start). The core frames, encodes (Opus), seals
 * (AEAD), and sends (UDP) the provided samples exactly as it would mic/loopback audio.
 *
 * pcm MUST be 48 kHz int16 — the core does NOT resample. (The whole audio engine runs at
 * 48 kHz; see docs/voice.md §3. A bot is responsible for resampling its source to 48 kHz.)
 *
 * samples_per_channel : samples per channel for THIS call. Any count is accepted — the core
 *     buffers and re-chunks to the channel's Opus frame size (the channel's frame_ms decides
 *     this: 480 @ 10 ms, 960 @ 20 ms, 1920 @ 40 ms, …). You need not match the frame size, and
 *     a channel with a non-20 ms window is handled transparently.
 * channels            : 1 (mono) or 2 (stereo interleaved L/R). VC_ERR_INVALID_ARG otherwise.
 *
 * Use cases: ReplayKit Broadcast Extension (iOS), ScreenCaptureKit (macOS), bots (TTS /
 * music / relay), soundboards, DAW integration. Works for any stream kind (MIC /
 * SCREEN_AUDIO / AUX_DEVICE). Thread-safe; may be called from any thread.
 *
 * Replaces vc_test_inject_capture (deprecated alias, see below). */
VC_API vc_result vc_stream_feed_pcm(vc_client* c, uint32_t stream_id,
                                    const int16_t* pcm, size_t samples_per_channel,
                                    uint32_t channels);

/* External PCM tap — receive decoded remote audio as int16 PCM per stream, before it is
 * summed into the hardware mix. The callback fires on the audio playback thread once per
 * decoded Opus frame (typically every 20 ms) for each active remote stream:
 *
 *   cb(user, user_id, stream_id, pcm, samples_per_channel, channels, sample_rate)
 *
 * user_id / stream_id : identify the sender (same values as VC_EVENT_STREAM_STARTED).
 * pcm                 : decoded int16 PCM, interleaved when channels == 2.
 * samples_per_channel : samples per channel for this frame (typically 960 @ 48 kHz).
 * channels            : 1 or 2, matching the sender's stream configuration.
 * sample_rate         : always 48000 in the current implementation.
 *
 * Pass cb = NULL to disable (default: disabled; hardware playback only).
 * The callback MUST NOT block, lock, or allocate — copy what you need and return.
 * PCM is still delivered to the hardware playback device regardless (dual output). */
typedef void (*vc_pcm_sink_cb)(void* user, uint32_t user_id, uint32_t stream_id,
                               const int16_t* pcm, size_t samples_per_channel,
                               uint32_t channels, uint32_t sample_rate);
VC_API vc_result vc_set_pcm_sink(vc_client* c, vc_pcm_sink_cb cb, void* user);

/* ── External playback (iOS VPIO / echo cancellation) ───────────────────────────
 * On iOS, real echo cancellation + noise suppression + AGC are provided ONLY by Apple's
 * Voice-Processing I/O audio unit (VPIO), which the Swift AVAudioEngine layer owns. For VPIO
 * to cancel echo, the remote-audio playback must go through the SAME VPIO unit as the mic
 * capture (VPIO subtracts the played-back signal from the mic). So in that topology the core
 * must NOT open/drive its own hardware playback device — its output would bypass VPIO, giving
 * it no reference signal and producing echo. Instead, enable external playback: the core keeps
 * decoding + mixing every remote stream on a steady ~20 ms cadence and delivers the FINAL
 * MIXED PCM (post output-volume, all streams summed) to this sink, which the Swift layer
 * renders through the VPIO output.
 *
 *   cb(user, pcm, samples_per_channel, channels, sample_rate)
 *
 * pcm                 : final mixed int16 PCM, interleaved when channels == 2.
 * samples_per_channel : samples per channel for this block (960 @ 20 ms / 48 kHz).
 * channels            : the engine's playback channel count (2 = stereo).
 * sample_rate         : always 48000.
 *
 * The callback fires on the core's mixer-timer thread (NOT a hardware audio thread). It fires
 * steadily even with no remote streams (a silent block), so the renderer has a continuous
 * clock. The callback MUST NOT block, lock, or allocate — copy into a lock-free ring and
 * return. Independent of vc_set_pcm_sink (the per-stream tap), which still works. Pass cb=NULL
 * to disable (default: disabled). */
typedef void (*vc_mixed_output_cb)(void* user, const int16_t* pcm,
                                   size_t samples_per_channel, uint32_t channels,
                                   uint32_t sample_rate);
VC_API vc_result vc_set_mixed_output_sink(vc_client* c, vc_mixed_output_cb cb, void* user);

/* Enable/disable external-playback mode (default: disabled = normal hardware playback). When
 * enabled, the core does NOT open a hardware playback device; decode+mix runs on an internal
 * ~20 ms timer and the result is delivered via vc_set_mixed_output_sink. To also bypass the
 * hardware mic (feeding VPIO-processed mic PCM instead), start the MIC stream with
 * vc_stream_desc.external_feed=1 and push frames via vc_stream_feed_pcm — the core then skips
 * the hardware capture device too. Apply BEFORE the engine starts, or follow with
 * vc_audio_restart() to apply to a running engine. `enable` is a bool (0/1). */
VC_API vc_result vc_set_external_playback(vc_client* c, int enable);

/* ── Text ─────────────────────────────────────────────────────────────────── */
VC_API vc_result vc_send_text(vc_client* c, vc_text_scope scope, uint32_t target_id,
                              const char* utf8);

/* ── Device enumeration (for UI pickers) ──────────────────────────────────── */
VC_API vc_result vc_list_devices(vc_client* c, vc_device_kind kind, vc_device_list* out);
VC_API void vc_free_device_list(vc_device_list* list);

/* ── Channel / user / stream enumeration (M4; mirrors vc_list_devices above) ─────────────── */
VC_API vc_result vc_list_channels(vc_client* c, vc_channel_list* out);
VC_API void      vc_free_channel_list(vc_channel_list* list);

VC_API vc_result vc_list_users(vc_client* c, vc_user_list* out);
VC_API void      vc_free_user_list(vc_user_list* list);

/* Streams currently owned by user_id (their mic/screen-audio/aux), per the last snapshot/
 * event. VC_ERR_INVALID_ARG if user_id is unknown. */
VC_API vc_result vc_list_user_streams(vc_client* c, uint32_t user_id,
                                      vc_stream_summary_list* out);
VC_API void      vc_free_stream_summary_list(vc_stream_summary_list* list);

/* ── TOFU server-identity confirmation (M4) — see VC_EVENT_SERVER_IDENTITY/vc_tofu_status ── */
/* Accept or reject the pending server-identity check for the in-progress connect(). Must be
 * called after a VC_EVENT_SERVER_IDENTITY event; the io_thread_ holds the connection open
 * (ClientHello/auth deferred) until this is called, up to a generous internal timeout (after
 * which it's treated as a reject). accept=0 aborts the connection (emits
 * VC_EVENT_DISCONNECTED, result=VC_ERR_CRYPTO) and does NOT update the pin file. accept=1 on
 * FIRST_CONNECT/MISMATCH updates the pin file to the new fingerprint and proceeds; accept=1 on
 * MATCHED is a no-op confirmation (always safe) and proceeds. VC_ERR_INVALID_ARG if no
 * identity confirmation is currently pending. */
VC_API vc_result vc_confirm_server_identity(vc_client* c, int accept /* bool */);

/* The Ed25519 identity fingerprint from ServerHello, hex-formatted for display (e.g. "this
 * server also identifies as <hex>"). Purely informational — NOT the value
 * vc_confirm_server_identity gates on (see vc_tofu_status's doc comment). Empty string if not
 * yet available. Pass out_buf=NULL to query the required buffer size via *out_len first;
 * otherwise out_buf must be >= *out_len + 1 bytes (NUL-terminated UTF-8/ASCII hex). */
VC_API vc_result vc_get_server_identity_display(vc_client* c, char* out_buf, size_t buf_cap,
                                                 size_t* out_len);

/* ── M5: Moderation & admin ─────────────────────────────────────────────────
 * All calls are async; the result arrives as VC_EVENT_GENERIC_RESULT (or
 * VC_EVENT_ACCOUNT_LIST for vc_list_accounts). They require VC_STATE_CONNECTED and,
 * on the server side, the appropriate permission. */

VC_API vc_result vc_kick_user(vc_client* c, uint32_t user_id, const char* reason);
VC_API vc_result vc_ban_user(vc_client* c, uint32_t user_id, const char* reason,
                             uint64_t expires_unix_ms);
VC_API vc_result vc_set_permission(vc_client* c, uint32_t user_id,
                                    const vc_permissions* perms);
VC_API vc_result vc_set_server_mute(vc_client* c, uint32_t user_id, int muted, int deafened);
VC_API vc_result vc_move_user(vc_client* c, uint32_t user_id, uint32_t channel_id);

VC_API vc_result vc_create_channel(vc_client* c, const vc_channel_info* info);
VC_API vc_result vc_edit_channel(vc_client* c, const vc_channel_info* info);
VC_API vc_result vc_delete_channel(vc_client* c, uint32_t channel_id);

VC_API vc_result vc_create_account(vc_client* c, const char* username, const char* password);
VC_API vc_result vc_reset_password(vc_client* c, const char* username,
                                    const char* new_password);
VC_API vc_result vc_delete_account(vc_client* c, const char* username);
VC_API vc_result vc_list_accounts(vc_client* c);

/* Pull the last received account list (populated when VC_EVENT_ACCOUNT_LIST fires).
 * Caller must free the list with vc_free_account_list. */
VC_API vc_result vc_get_account_list(vc_client* c, vc_account_list* out);
VC_API void      vc_free_account_list(vc_account_list* list);

/* Pull the caller's own permissions (from the last AuthResult). */
VC_API vc_result vc_get_permissions(vc_client* c, vc_permissions* out);

/* AVAudioSession interruption hooks (iOS M6). Pause/resume miniaudio device I/O for
 * AVAudioSession interruptions (phone call, Siri, etc.) and backgrounding. Call
 * vc_audio_suspend() when an interruption begins; call vc_audio_resume() after the
 * session is re-activated. No-op if the audio engine is not running. */
VC_API vc_result vc_audio_suspend(vc_client* c);
VC_API vc_result vc_audio_resume(vc_client* c);

/* Full audio engine restart (iOS M6). Unlike vc_audio_suspend/resume which only stop/start
 * the existing miniaudio devices (leaving them bound to the route that was active when they
 * were opened), vc_audio_restart() uninitializes and re-initializes the capture and playback
 * devices so they pick up a new AVAudioSession route. Call this from the Swift layer AFTER
 * reconfiguring AVAudioSession (setCategory, setPreferredInput, setPreferredPolarPattern, etc.)
 * so the core's devices reopen against the new route.
 * Safe to call when the engine is not running (it will just start it). */
VC_API vc_result vc_audio_restart(vc_client* c);

#if defined(__cplusplus)
} /* extern "C" */
#endif

#endif /* VOICECAT_H */
