package wang.yaojia.webterm.wiring

import kotlinx.coroutines.flow.Flow
import wang.yaojia.webterm.hostregistry.LastSessionStore
import wang.yaojia.webterm.session.Adopted
import wang.yaojia.webterm.session.Exited
import wang.yaojia.webterm.session.SessionEvent

/**
 * # SessionActivityBridge (A29) — drives the [LastSessionStore] off the engine event stream.
 *
 * The lifecycle half of the continue-last-session cold-start UX (plan §1, §5 A29). One bridge is minted
 * PER SESSION, bound to the [hostId] the session dials (like [EventBus], it is NOT an app singleton and
 * has no Hilt binding — it is constructed inside the session unit and dies with it). It observes the
 * session's control-event stream and folds exactly two lifecycle events into the store:
 *
 *  - **`Adopted` → SET** — persist the SERVER-ISSUED id ([Adopted.sessionId]). The server always adopts
 *    its own id (a reconnect may hand back a new one), so the LATEST adopted id is always the one to
 *    offer as "continue last"; re-adopting simply overwrites.
 *  - **`Exited` → CLEAR** — a dead shell must NOT be offered as "continue last" (else cold-start would
 *    silently spawn a brand-new session), so the shell exiting clears the stored id ([LastSessionStore]).
 *
 * Every OTHER event — including `Connection(Closed)` (a mere DETACH: the server-side PTY keeps running,
 * so the session is STILL continuable) — leaves the store untouched. Only a real `Exited` clears it.
 *
 * Keyed by [hostId] so distinct hosts never clobber each other's last-session pointer.
 *
 * ### Purity / testability
 * PURE Kotlin (coroutines only, no Android import) so the set-on-adopted / clear-on-exited behaviour is
 * unit-testable under `runTest` by driving [observe] with a `flowOf(...)` and a real
 * [InMemoryLastSessionStore][wang.yaojia.webterm.hostregistry.InMemoryLastSessionStore] double.
 */
public class SessionActivityBridge(
    private val lastSessionStore: LastSessionStore,
    private val hostId: String,
) {
    /**
     * Observe [events] for the whole session lifetime, applying each to the [LastSessionStore]. Returns
     * when [events] completes (the engine closed its stream) or the collecting scope is cancelled. The
     * caller launches this on the session's scope and collects the controller's control-event stream.
     */
    public suspend fun observe(events: Flow<SessionEvent>) {
        events.collect { record(it) }
    }

    /**
     * Fold a single [event] into the store: `Adopted` sets the server-issued id, `Exited` clears it, and
     * every other event (output / connection / gate / telemetry / digest) is a no-op.
     */
    public suspend fun record(event: SessionEvent) {
        when (event) {
            is Adopted -> lastSessionStore.setLastSessionId(event.sessionId, hostId)
            is Exited -> lastSessionStore.setLastSessionId(null, hostId)
            else -> Unit
        }
    }
}
