// Callbacks — the C function pointers passed to `vc_callbacks`. These are the Swift
// equivalent of the C# client's `[UnmanagedCallersOnly]` static methods (NativeCallbacks.cs).
//
// The critical patterns (carried over from the proven C# implementation):
//   1. `@convention(c)` closures — plain C function pointers, NOT GC/ARC-managed closures.
//      A @convention(c) closure cannot capture context, which is why the `user` pointer is
//      used to resolve back to the VoiceCatClient instance (the C# version uses GCHandle for
//      the same thing; Swift uses Unmanaged).
//   2. `Unmanaged.passUnretained(self).toOpaque()` as the `user` context — a stable raw
//      pointer to the Swift object WITHOUT incrementing the retain count. This is safe
//      because `deinit` calls `vc_client_destroy` (which synchronously joins every internal
//      thread) BEFORE the object's memory is freed — so no callback can fire after the object
//      is gone. (The C# equivalent: GCHandle.Alloc + GCHandle.Free in Dispose.)
//   3. Copy `ev.text` to a Swift `String` INSIDE `onEvent` (via `VoiceCatEvent.from(_:)`)
//      before returning — the raw pointer is dangling after the callback returns. This is
//      the #1 lifetime rule from voicecat.h's vc_event doc comment.

import VoiceCatC
import Foundation

/// Internal: builds the `vc_callbacks` struct wired to VoiceCatClient's C function pointers.
/// The `user` context is an Unmanaged-passUnretained pointer to the client — resolved back
/// to the client inside `onEvent`/`onLevel` below.
internal enum Callbacks {
    /// The `on_event` C function pointer. Non-capturing @convention(c) closure — resolves
    /// the VoiceCatClient from `user` and enqueues a safe copy of the event.
    static let onEvent: @convention(c) (
        UnsafeMutableRawPointer?, UnsafePointer<vc_event>?
    ) -> Void = { user, ev in
        guard let user, let ev else { return }
        let client = Unmanaged<VoiceCatClient>.fromOpaque(user).takeUnretainedValue()
        // Copy the event (including text) to a Swift value NOW — the raw vc_event is
        // invalid after this callback returns.
        client.enqueueEvent(VoiceCatEvent.from(ev.pointee))
    }

    /// The `on_level` C function pointer. Coalesces to "latest sample per stream_id"
    /// (intermediate values are visually irrelevant — same as C#'s ConcurrentDictionary).
    static let onLevel: @convention(c) (
        UnsafeMutableRawPointer?, UInt32, Float
    ) -> Void = { user, streamId, rms in
        guard let user else { return }
        let client = Unmanaged<VoiceCatClient>.fromOpaque(user).takeUnretainedValue()
        client.enqueueLevel(streamId, rms)
    }

    /// Construct the vc_callbacks struct for a given client.
    static func make(user: UnsafeMutableRawPointer) -> vc_callbacks {
        vc_callbacks(on_event: onEvent, on_level: onLevel, user: user)
    }
}
