//
//  Vici.swift
//  SwanDeskHelper — thin Swift wrapper over strongSwan's libvici (CVici module).
//
//  Subscribes to charon's VICI ike-updown / child-updown events over its unix
//  socket; those events drive the menu-bar status. (Config loading and
//  initiate/terminate go through the bundled `swanctl` CLI, not VICI.)
//

import Foundation
import CVici

/// A live VICI connection to charon, used to receive events.
final class ViciConnection {
    private let conn: OpaquePointer
    /// Called for every registered event, on libvici's internal thread.
    var onEvent: ((_ name: String, _ res: OpaquePointer) -> Void)?

    private var registeredEvents: [String] = []
    private var isClosed = false
    private static var didInit = false

    init?(uri: String? = nil) {
        if !ViciConnection.didInit { vici_init(); ViciConnection.didInit = true }
        let c: OpaquePointer?
        if let uri = uri {
            c = uri.withCString { vici_connect(UnsafeMutablePointer(mutating: $0)) }
        } else {
            c = vici_connect(nil)
        }
        guard let conn = c else { return nil }
        self.conn = conn
    }

    deinit {
        close()
    }

    /// Register for a named event stream.
    @discardableResult
    func register(event: String) -> Bool {
        let user = Unmanaged.passUnretained(self).toOpaque()
        let rc = event.withCString {
            vici_register(conn, UnsafeMutablePointer(mutating: $0), viciEventTrampoline, user)
        }
        if rc == 0 { registeredEvents.append(event) }
        return rc == 0
    }

    /// Unregister all events, then disconnect. Idempotent. Call this explicitly
    /// (on the owner's queue) before dropping the reference: unregistering stops
    /// new event deliveries, and `vici_disconnect` then tears down the connection —
    /// avoiding a callback racing ARC deallocation. The callback only ever does a
    /// `queue.async` hop, so this is the safe shutdown order.
    func close() {
        guard !isClosed else { return }
        isClosed = true
        for event in registeredEvents {
            _ = event.withCString {
                vici_register(conn, UnsafeMutablePointer(mutating: $0), nil, nil)
            }
        }
        registeredEvents.removeAll()
        vici_disconnect(conn)
    }

    fileprivate func dispatchEvent(name: String, res: OpaquePointer) {
        onEvent?(name, res)
    }
}

/// C-callable trampoline: libvici calls this with the `user` pointer we passed to
/// vici_register; we forward to the owning ViciConnection.
private func viciEventTrampoline(user: UnsafeMutableRawPointer?, name: UnsafeMutablePointer<CChar>?, res: OpaquePointer?) {
    guard let user = user, let res = res else { return }
    let conn = Unmanaged<ViciConnection>.fromOpaque(user).takeUnretainedValue()
    let eventName = name.map { String(cString: $0) } ?? ""
    conn.dispatchEvent(name: eventName, res: res)
}
