import Foundation
import WireProtocol

/// The server's `TimelineClass` vocabulary (src/types.ts:423, produced by
/// src/session/timeline.ts CLASS_MAP). Kept in lockstep with
/// `TimelineEvent.knownClasses` (WireProtocol) — a sync test asserts equality.
/// Internal (not public API): consumers see counts, not class strings.
enum TimelineClassName {
    static let tool = "tool"
    static let waiting = "waiting"
    static let done = "done"
    static let stuck = "stuck"
    static let user = "user"
}

/// "What happened while I was away" summary (T-iOS-6, plan §3.2 — frozen
/// shape). Reduced once after a reconnect from the session's activity timeline
/// (`GET /live-sessions/:id/events`, src/types.ts:428-433) and rendered above
/// the terminal (T-iOS-14).
public struct AwayDigest: Sendable, Equatable {
    /// Events with class `tool` at/after `since` (PreToolUse + PostToolUse).
    public let toolRuns: Int
    /// Events with class `waiting` (approval requests / notifications).
    public let waitingCount: Int
    /// Any `done` event seen (Stop / SessionEnd).
    public let sawDone: Bool
    /// Any `stuck` event seen (A5 silent-too-long derivation).
    public let sawStuck: Bool
    /// The newest events (post-`since`), oldest→newest, truncated to `limit`.
    public let recent: [TimelineEvent]

    /// The all-zero digest: nothing happened → UI skips rendering entirely.
    public static let empty = AwayDigest(
        toolRuns: 0, waitingCount: 0, sawDone: false, sawStuck: false, recent: [])

    public init(
        toolRuns: Int, waitingCount: Int, sawDone: Bool, sawStuck: Bool,
        recent: [TimelineEvent]
    ) {
        self.toolRuns = toolRuns
        self.waitingCount = waitingCount
        self.sawDone = sawDone
        self.sawStuck = sawStuck
        self.recent = recent
    }

    /// True when there is nothing to show (the UI's "don't render" signal).
    public var isEmpty: Bool { self == .empty }

    /// Folds a timeline into a digest. PURE function — no I/O, no clock.
    ///
    /// - `events`: `/events` entries (untrusted server input; order not assumed
    ///   — entries are stably re-ordered by `at` before truncation).
    /// - `since`: the moment the user left; strictly-earlier events are
    ///   excluded, an event AT the instant still counts.
    /// - `limit`: max `recent` entries kept (newest win); `<= 0` keeps none.
    public static func reduce(events: [TimelineEvent], since: Date, limit: Int) -> AwayDigest {
        let sinceMs = clampedEpochMs(since)
        let away = events
            .filter { $0.at >= sinceMs }
            .sorted { $0.at < $1.at }
        guard !away.isEmpty else { return .empty }

        return AwayDigest(
            toolRuns: away.filter { $0.class == TimelineClassName.tool }.count,
            waitingCount: away.filter { $0.class == TimelineClassName.waiting }.count,
            sawDone: away.contains { $0.class == TimelineClassName.done },
            sawStuck: away.contains { $0.class == TimelineClassName.stuck },
            recent: Array(away.suffix(max(0, limit))))
    }

    /// `Date` → epoch-ms `Int`, clamped: `Int(_: Double)` TRAPS on overflow, and
    /// `since` is caller input (e.g. `.distantFuture`) — clamp instead of crash.
    private static func clampedEpochMs(_ date: Date) -> Int {
        let ms = date.timeIntervalSince1970 * 1_000
        guard ms < Double(Int.max) else { return .max }
        guard ms > Double(Int.min) else { return .min }
        return Int(ms)
    }
}
