//
//  VPNHelperProtocol.swift
//  SwanDesk — shared between the app and the privileged helper.
//
//  XPC contract between SwanDesk.app and the root daemon fi.juvex.swandeskhelper.
//  The daemon owns the strongSwan `charon` process, the VICI session, route +
//  DNS orchestration and the keepalive loop; the app drives it and renders status.
//

import Foundation

struct HelperConstants {
    /// Mach service the daemon publishes and the app connects to. Must match the
    /// `MachServices` key in SwanDeskHelperDaemon.plist and the helper's embedded
    /// Info.plist CFBundleIdentifier.
    static let machServiceName = "fi.juvex.swandeskhelper"
}

/// Functions the app may call in the privileged helper.
///
/// Access is gated by the daemon's code-signing check (`isClientTrusted`): only a
/// genuine, Apple-notarized, Juvex-signed SwanDesk.app can reach these. There is
/// no separate admin-authorization prompt — connecting/disconnecting the VPN does
/// not pop Touch ID / a password.
@objc(VPNHelperProtocol)
protocol VPNHelperProtocol {
    /// CFBundleVersion of the running daemon — used by the app to detect a stale
    /// (older) installed daemon and force a clean re-register. See `Helper`.
    func getVersion(reply: @escaping (String) -> Void)

    /// Bring a tunnel up for the given profile — other tunnels stay up (several
    /// profiles can be connected at once). `configData` is a JSON-encoded
    /// `VPNProfile` (non-secret fields, including its list `priority`); `secrets`
    /// is a JSON-encoded `VPNSecrets` (xauth password + private key PEM) passed
    /// transiently so nothing secret is persisted outside the Keychain and the
    /// helper's root-owned staging dir.
    /// Returns (status, message): 0 = connected, non-zero = failure with detail.
    func start(configData: Data, secrets: Data, reply: @escaping (Int, String) -> Void)

    /// Tear down the tunnel for one profile (string form of `VPNProfile.id`):
    /// terminate its IKE SA, release its routes/DNS claims to the next tunnel in
    /// priority order, and stop charon when it was the last tunnel. Safe to call
    /// when that profile is not connected.
    func stop(profileID: String, reply: @escaping (Int, String) -> Void)

    /// Tear down every active tunnel (used on app quit). Safe when none is up.
    func stopAll(reply: @escaping (Int, String) -> Void)

    /// Emergency stop: SIGKILL any charon/dnsmasq (even strays the helper lost
    /// track of), remove routes, restore DNS/IPv6, drop kill-switch firewall
    /// rules, and wipe the staging dir. For when the UI says "disconnected" but
    /// strongSwan is still running underneath.
    func forceKillAll(reply: @escaping (Int, String) -> Void)

    /// Re-order the active tunnels after the user rearranges the profile list.
    /// `profileIDs` is every profile id in the new list order (top = highest
    /// priority); the helper re-resolves route/DNS/dnsmasq conflicts to match.
    func setPriorities(profileIDs: [String], reply: @escaping () -> Void)

    /// Current runtime status as a JSON-encoded `VPNRuntimeStatus`.
    func status(reply: @escaping (Data) -> Void)

    /// Register the connecting app to receive pushed status/log callbacks. The app
    /// sets its connection's `exportedObject` to a `VPNClientProtocol` first; the
    /// helper then pushes events until that connection invalidates.
    func subscribe(reply: @escaping (Bool) -> Void)

    /// Called by the app when it detects the on-disk helper binary changed (i.e.
    /// after a rebuild). The helper replies, then sends itself SIGTERM so its
    /// existing teardown path (stopSync → exit) runs and launchd restarts it
    /// pointing at the newly-registered binary.
    func terminate(reply: @escaping () -> Void)
}

/// Functions the helper calls back on the app (reverse XPC) to push live state.
@objc(VPNClientProtocol)
protocol VPNClientProtocol {
    /// A JSON-encoded `VPNRuntimeStatus` snapshot whenever the SA state changes.
    func statusChanged(_ data: Data)
    /// One line of charon/engine log for the live log pane.
    func logLine(_ line: String)
}
