package wang.yaojia.webterm.wire

import kotlinx.coroutines.flow.Flow

/**
 * The ONLY WS I/O boundary (FROZEN contract, plan §3.1/§3.2). The Android analogue of iOS
 * `WireProtocol.TermTransport`.
 *
 * `:transport-okhttp` (A7) and the `FakeTransport` in `:test-support` (A3) both implement this;
 * `SessionEngine` (A14) cannot tell them apart. Pure interface — NO OkHttp/Android imports.
 */
public interface TermTransport {
    /**
     * Open a WS connection to [HostEndpoint.wsUrl], stamping `Origin: endpoint.originHeader` on
     * the upgrade (CSWSH defence, plan §5.1). Throws on connect-time failure (the underlying
     * error is rethrown verbatim so the pairing probe, A9, can classify POSIX/TLS codes).
     */
    public suspend fun connect(endpoint: HostEndpoint): TransportConnection
}

/**
 * One live WS connection, as an immutable capability handle (FROZEN contract). The Android
 * analogue of iOS `WireProtocol.TransportConnection` (Swift's closure struct becomes an
 * interface here so both the OkHttp impl and the fake can implement it).
 */
public interface TransportConnection {
    /**
     * Server JSON text frames, in arrival order. Normal completion of the flow = a clean close;
     * an exception thrown by the flow = a transport error. The two MUST stay distinguishable
     * (the engine treats clean-close vs error differently, A14).
     */
    public val frames: Flow<String>

    /** Send one client JSON text frame (produced by A4's `MessageCodec.encode`). */
    public suspend fun send(frame: String)

    /** Close the connection (client detach — the server-side PTY keeps running). */
    public suspend fun close()
}

/**
 * One live connection's ping capability (see [PingableTermTransport]). Android analogue of the
 * iOS SessionCore-internal `ConnectionPinger`.
 */
public interface ConnectionPinger {
    /**
     * Send one WS ping; returns true iff the pong arrived in time (false = a miss, counted by
     * the PingScheduler against [Tunables.PONG_MISS_LIMIT]).
     */
    public suspend fun ping(): Boolean
}

/**
 * A [TermTransport] that also exposes a per-connection ping hook, so the pure `PingScheduler`
 * (A5) can drive keep-alive pings only through transports that support it. Android analogue of
 * the iOS `transport as? any PingableTermTransport` downcast: `SessionEngine` opens the
 * connection via [connectPingable] when the transport implements this, otherwise falls back to
 * plain [TermTransport.connect]. The `FakeTransport` (A3) intentionally does NOT implement this,
 * so engine tests drive the PingScheduler through an injected closure instead.
 */
public interface PingableTermTransport : TermTransport {
    /**
     * Like [connect], but also hands back the ping hook for that same connection (for
     * PingScheduler wiring).
     */
    public suspend fun connectPingable(endpoint: HostEndpoint): PingableConnection
}

/** The frozen [TransportConnection] PLUS its ping capability (iOS: a `(connection, pinger)` tuple). */
public data class PingableConnection(
    val connection: TransportConnection,
    val pinger: ConnectionPinger,
)
