import Foundation
import NucleicProtocol

/// The authority seam between the sync server and the running host (RUNTIME §1; the phone is
/// "another renderer of the same authority"). `SyncHost` depends only on this, so it's
/// testable with a fake and the real `AppStore` conformance stays thin.
///
/// All methods hop to wherever the authority lives (the `AppStore` conformance is
/// `@MainActor`); callers `await` them off the connection tasks.
public protocol SyncHostBridge: Sendable {
    var hostInfo: HostInfo { get async }
    var capabilities: WireCapabilities { get async }
    /// The model/effort catalog the phone's pickers mirror (SYNC §5.2), sent in `Welcome`.
    var modelCatalog: WireModelCatalog { get async }

    /// Current session list, newest-activity first (the phone sorts attention-first itself).
    func sessionSummaries() async -> [WireSessionSummary]

    /// Snapshot for a cold/warm subscribe: status + pending approvals + a tail (cold) or
    /// the events after `sinceSeq` (warm). Returns nil if the session is unknown.
    func snapshot(_ id: SessionID, sinceSeq: UInt64?) async -> SessionSnapshot?

    /// First-responder-wins approval (SYNC §5.4). Throws `WireError(.alreadyResolved)` if the
    /// race was lost, `.unknownSession` if gone.
    func respondToApproval(_ id: ApprovalID, _ decision: Decision) async throws

    /// Queue a follow-up user turn (scope ≥ approve).
    func sendInput(_ id: SessionID, _ input: AgentInput) async throws

    /// Cooperative interrupt (scope ≥ control).
    func interrupt(_ id: SessionID) async throws

    // Control scope: the same actions the Mac can take (UX_IOS §10, lifted to control).
    func dashboardSnapshot() async -> DashboardSnapshot
    func startChat(_ request: StartChatRequest) async throws
    func captureTodo(_ request: CaptureTodoRequest) async throws
    func dispatchTodo(_ id: TodoID, in projectID: ProjectID) async throws
    func remoteSetTodoStatus(_ id: TodoID, _ status: TodoStatus) async throws
    func remoteDeleteTodo(_ id: TodoID) async throws
    func renameSession(_ id: SessionID, to title: String) async throws
    func setFavorite(_ id: SessionID, _ favorite: Bool) async throws
    func setArchived(_ id: SessionID, _ archived: Bool) async throws
    func deleteSessionRemote(_ id: SessionID) async throws
    func integrate(_ id: SessionID, _ mode: IntegrationMode) async throws
    func discard(_ id: SessionID) async throws
    // Mid-session model / reasoning / automation changes (the Mac header's affordances).
    func setSessionModel(_ id: SessionID, _ model: String?) async throws
    func setSessionEffort(_ id: SessionID, _ effort: String?) async throws
    func setSessionAuto(_ id: SessionID, _ auto: Bool) async throws
    func setSessionAutoShip(_ id: SessionID, _ autoShip: Bool) async throws
    func setSessionShipBranch(_ id: SessionID, _ branch: String?) async throws

    /// Live host-side changes to fan out to subscribed clients. One shared stream; `SyncHost`
    /// multiplexes per-connection filtering/verbosity on top.
    func broadcasts() async -> AsyncStream<HostBroadcast>
}

/// The wire `SessionSummary` lives in NucleicProtocol but collides by name with the app's
/// own view-model `SessionSummary`; alias it so host code is unambiguous.
public typealias WireSessionSummary = NucleicProtocol.SessionSummary

/// A host-side change worth pushing to subscribers. `SyncHost` translates these into
/// `HostMsg`s, filtered by each connection's subscriptions and verbosity.
public enum HostBroadcast: Sendable {
    case events(SessionID, [AgentEvent])
    case approvalRequested(ApprovalRequest)
    case approvalResolved(ApprovalResolved)
    case sessionUpdated(WireSessionSummary)
    case dashboard(DashboardSnapshot)
}
