import Foundation

struct ExtensionEditorTextUpdate: Equatable, Sendable {
    let sessionId: String
    let text: String
    let revision: Int
}

/// Observable state bag for per-connection chat UI concerns.
///
/// Extracted from `ServerConnection` to isolate view-model properties
/// (model/slash command caches, file suggestions, thinking level)
/// from transport and networking state. Views that only need these properties
/// observe `ChatSessionState` instead of the full `ServerConnection`.
///
/// Owned by `ServerConnection` as a `let` property. Server message handlers
/// write to it; views read from it via `@Environment(ChatSessionState.self)`.
@MainActor @Observable
final class ChatSessionState {

    // MARK: - Composer

    /// Extension-driven editor updates (`ctx.ui.setEditorText`) with revision
    /// tokens so repeated identical payloads still trigger view updates.
    var extensionEditorTextUpdate: ExtensionEditorTextUpdate?
    @ObservationIgnored private var extensionEditorTextRevision = 0

    // MARK: - Scroll restoration (non-observed, persisted via RestorationState)

    @ObservationIgnored var scrollAnchorItemId: String?
    @ObservationIgnored var scrollWasNearBottom: Bool = true

    // MARK: - Thinking level

    var thinkingLevel: ThinkingLevel = .medium

    // MARK: - Slash commands

    var slashCommands: [SlashCommand] = []
    var slashCommandsCacheKey: String?
    var slashCommandsRequestId: String?
    var slashCommandsTask: Task<Void, Never>?

    // MARK: - Model cache

    var cachedModels: [ModelInfo] = []
    var modelsCacheReady = false
    var modelPrefetchTask: Task<Void, Never>?

    // MARK: - File suggestions

    var fileSuggestions: [FileSuggestion] = []
    var fileSuggestionTask: Task<Void, Never>?

    // MARK: - Model Cache

    /// Refresh the model cache from the server.
    /// Called by views that need fresh model data (ModelPickerSheet, QuickSessionSheet).
    func refreshModelCache(api: APIClient) async {
        do {
            cachedModels = try await api.listModels()
            modelsCacheReady = true
        } catch {
            // Non-fatal — keep stale cached data
        }
    }

    // MARK: - Lifecycle

    /// Cancel all in-flight background tasks.
    func cancelTasks() {
        slashCommandsTask?.cancel()
        slashCommandsTask = nil
        slashCommandsRequestId = nil
        slashCommandsCacheKey = nil
        fileSuggestionTask?.cancel()
        fileSuggestionTask = nil
        modelPrefetchTask?.cancel()
        modelPrefetchTask = nil
    }

    func stageExtensionEditorText(text: String, sessionId: String) {
        extensionEditorTextRevision &+= 1
        extensionEditorTextUpdate = ExtensionEditorTextUpdate(
            sessionId: sessionId,
            text: text,
            revision: extensionEditorTextRevision
        )
    }

    /// Reset all cached state (called on session disconnect).
    func resetSessionState() {
        cancelTasks()
        slashCommands = []
        fileSuggestions = []
        extensionEditorTextUpdate = nil
    }
}
