import Foundation
import WireProtocol

/// Events `SessionEngine` publishes to the UI (frozen contract, plan §3.2).
/// The engine's `events` stream is the UI's ONLY outlet: a single consumer
/// (the ViewModel, T-iOS-11) iterates it on the MainActor and feeds SwiftTerm,
/// banners, and gate UI from it.
public enum SessionEvent: Sendable, Equatable {
    /// Connection lifecycle. `.failed` is a NON-retryable terminal state — the
    /// engine will not reconnect (plan §3.2: `.replayTooLarge` must never enter
    /// the backoff loop).
    case connection(ConnectionState)
    /// The server confirmed the attach. ALWAYS adopt the server-issued id —
    /// attaching with an unknown UUID yields a brand-new session and a NEW id
    /// (src/session/manager.ts:166-173).
    case adopted(sessionId: UUID)
    /// Opaque terminal bytes; ring-buffer replay and live stream share this
    /// shape. Feed to SwiftTerm verbatim (the @MainActor hop is the VM's job).
    case output(String)
    /// The shell exited. `code == WireConstants.spawnFailedExitCode` (-1) means
    /// the spawn never succeeded (M4). Either way the session is over — the
    /// engine stops reconnecting and concludes with `.connection(.closed)`.
    case exited(code: Int, reason: String?)
    /// The held permission gate changed; `nil` = gate lifted (GateTracker,
    /// T-iOS-6). Carries the epoch the UI must hand back when deciding.
    case gate(GateState?)
    /// Latest statusLine telemetry broadcast (B2).
    case telemetry(StatusTelemetry)
    /// "What happened while I was away" — emitted exactly ONCE after each
    /// completed reconnect, reduced from the injected `eventsSource` over the
    /// events since the disconnect moment (plan §3.2).
    case digest(AwayDigest)
}

/// Connection lifecycle states (plan §3.2 comment on `SessionEvent.connection`).
public enum ConnectionState: Sendable, Equatable {
    /// `open()` was called; the first connect is being established.
    case connecting
    /// Live: transport up, attach handshake done, frames flowing.
    case connected
    /// The transport dropped; retry number `attempt` fires after `next`.
    /// Ladder mirrors the web client via `ReconnectMachine` (1s→…→30s cap).
    case reconnecting(attempt: Int, next: Duration)
    /// Ended deliberately: client `close()` (detach — the server-side PTY keeps
    /// running) or the session exited.
    case closed
    /// NON-retryable terminal failure — the engine stopped for good; the UI
    /// shows actionable copy instead of a spinner.
    case failed(FailureReason)
}

/// Why the engine gave up (plan §3.2 "不可重试终态").
public enum FailureReason: Sendable, Equatable {
    /// The single-frame ring-buffer replay exceeded
    /// `Tunables.maxWSMessageBytes` (EMSGSIZE(40), T-iOS-2 spike): reconnecting
    /// would deterministically hit the same wall forever, so the engine never
    /// enters backoff. UI copy: lower the server's SCROLLBACK_BYTES or raise
    /// the client cap (plan §3.2.1 coupling warning).
    case replayTooLarge
}
