import Foundation

/// A persisted, derived rollup of one session's transcript activity — the home
/// dashboard's message count and per-day histogram, cached so the dashboard never
/// has to re-scan transcripts (RUNTIME §4). Keyed by session; rebuildable at any time
/// from the transcript, so it's a pure cache. `lastSeq` is the transcript cursor the
/// rollup was computed at: when a session's live `lastSeq` advances past it the entry
/// is stale and gets recomputed.
public struct ActivityCacheEntry: Sendable, Equatable {
    public let lastSeq: UInt64
    /// Total user messages in the transcript.
    public let messageCount: Int
    /// User-message counts bucketed by start-of-day.
    public let activityByDay: [Date: Int]
    /// Total tokens used across the transcript (every turn's input + cache + output + reasoning;
    /// see `Usage.totalTokens`). Drives the dashboard's total-tokens metric.
    public let tokenCount: Int
    /// Tokens-used totals bucketed by start-of-day — the per-day series the activity grid shades
    /// by (falling back to `activityByDay` for days with no recorded usage) and the backing for a
    /// token time series. Empty for transcripts that predate usage capture.
    public let tokensByDay: [Date: Int]

    public init(
        lastSeq: UInt64, messageCount: Int, activityByDay: [Date: Int],
        tokenCount: Int = 0, tokensByDay: [Date: Int] = [:]
    ) {
        self.lastSeq = lastSeq
        self.messageCount = messageCount
        self.activityByDay = activityByDay
        self.tokenCount = tokenCount
        self.tokensByDay = tokensByDay
    }
}

/// The metadata persistence surface (RUNTIME §4). Event bodies stay in the JSONL
/// transcript; this stores only what must be *queried* — session lists, status,
/// resume pointers, the live approval queue, and the derived dashboard activity cache.
///
/// SessionController depends on this protocol, not on GRDB directly, so the event
/// pipeline stays storage-agnostic and tests can use an in-memory fake.
public protocol SessionMetadataStore: Sendable {
    func saveProject(_ project: Project) async throws
    func loadProjects() async throws -> [Project]
    func deleteProject(id: ProjectID) async throws

    /// Insert-or-update a session row (the SessionController calls this as metadata
    /// changes — status transitions, lastSeq, diffstat).
    func saveSession(_ session: Session) async throws
    func loadSession(id: SessionID) async throws -> Session?
    func loadSessions(projectID: ProjectID) async throws -> [Session]
    /// Every session across all projects in one query — the launch/dashboard fast path,
    /// so the sidebar and dashboard load without a per-project round-trip.
    func loadAllSessions() async throws -> [Session]
    func deleteSession(id: SessionID) async throws

    /// The full dashboard activity cache, keyed by session id.
    func loadActivityCache() async throws -> [SessionID: ActivityCacheEntry]
    /// Insert-or-update one session's cached activity rollup.
    func saveActivityCache(_ sessionID: SessionID, _ entry: ActivityCacheEntry) async throws

    /// Record an approval as pending (resolved_at IS NULL).
    func saveApproval(_ request: ApprovalRequest) async throws
    /// Mark an approval resolved with the winning decision.
    func resolveApproval(_ resolved: ApprovalResolved) async throws
    func pendingApprovals(sessionID: SessionID) async throws -> [ApprovalRequest]

    /// Insert-or-update a captured idea (the home "to-do" inbox).
    func saveTodo(_ todo: Todo) async throws
    /// All todos, most-recently-updated first.
    func loadTodos() async throws -> [Todo]
    func deleteTodo(id: TodoID) async throws
}
