package wang.yaojia.webterm.session

import kotlin.time.Duration

/**
 * Events the SessionEngine (A14) publishes to the UI (FROZEN contract, plan §5). The Android
 * analogue of iOS `SessionCore.SessionEvent`. A single event type carries every engine->UI
 * signal so the UI consumes one stream with one `when`.
 *
 * SPLIT-FILE HIERARCHY (ownership, plan §5): this file (A5) freezes the sealed interface plus the
 * connection-lifecycle subtypes ([Connection], [Adopted], [Output], [Exited]). The cockpit
 * subtypes (`Gate`, `Digest`, `Telemetry`, which carry A6-owned payloads GateState/AwayDigest/
 * StatusTelemetry) are appended by A6 in `SessionEvent-cockpit.kt`. Kotlin only allows a sealed
 * hierarchy to be extended within the SAME module+package and a nested subtype cannot be added
 * from another file, so ALL subtypes are declared TOP-LEVEL (not nested under the interface) to
 * keep A5's and A6's additions uniform — every event reads as `is Output`, `is Gate`, ...
 */
public sealed interface SessionEvent

/**
 * Connection lifecycle changed. `.failed` inside [ConnectionState] is a NON-retryable terminal
 * state — the engine will not reconnect.
 */
public data class Connection(val state: ConnectionState) : SessionEvent

/**
 * The server confirmed the attach. ALWAYS adopt the server-issued id — attaching with an unknown
 * id yields a brand-new session and a NEW id (`src/session/manager.ts`). The wire carries the id
 * as a string ([wang.yaojia.webterm.wire.ServerMessage.Attached]).
 */
public data class Adopted(val sessionId: String) : SessionEvent

/** Opaque terminal bytes; ring-buffer replay and the live stream share this shape. Feed verbatim. */
public data class Output(val data: String) : SessionEvent

/**
 * The shell exited (terminal). `code == WireConstants.SPAWN_FAILED_EXIT_CODE` (-1) means the spawn
 * never succeeded. Either way the session is over — the engine stops reconnecting.
 */
public data class Exited(val code: Int, val reason: String? = null) : SessionEvent

/** Connection lifecycle states carried by [Connection]. */
public sealed interface ConnectionState {
    /** `open()` was called; the first connect is being established. */
    public data object Connecting : ConnectionState

    /** Live: transport up, attach handshake done, frames flowing. */
    public data object Connected : ConnectionState

    /**
     * The transport dropped; retry number [attempt] fires after [next]. The ladder mirrors the
     * web client via [ReconnectMachine] (1s -> ... -> 30s cap).
     */
    public data class Reconnecting(val attempt: Int, val next: Duration) : ConnectionState

    /** Ended deliberately: client `close()` (detach — the server-side PTY keeps running) or exit. */
    public data object Closed : ConnectionState

    /** NON-retryable terminal failure — the engine stopped for good; the UI shows actionable copy. */
    public data class Failed(val reason: FailureReason) : ConnectionState
}

/** Why the engine gave up (non-retryable terminal reasons). */
public enum class FailureReason {
    /**
     * The single-frame ring-buffer replay exceeded [wang.yaojia.webterm.wire.Tunables.MAX_WS_MESSAGE_BYTES]:
     * reconnecting would deterministically hit the same wall forever, so the engine never enters
     * back-off. UI copy: lower the server's SCROLLBACK_BYTES or raise the client cap.
     */
    REPLAY_TOO_LARGE,
}
