import ActivityKit
import Foundation

/// The one aggregate Live Activity (UX_IOS §5.3, aggregate per §11.4 — a single Activity
/// respects iOS's budget better than one per session). It answers "what is Nucleic doing right
/// now?" at a glance: the headline counts (running / waiting / approvals), the total churn in
/// flight, and a few of the most urgent sessions with their state and diff. Shared between the
/// app (which starts/updates it) and the widget extension (which renders it).
///
/// Deliberately free of `NucleicProtocol` types — the widget extension links only this file, so
/// the state carries plain value types and its own small `Backend`/`Kind` enums rather than the
/// wire `BackendID`/`SessionStatus`.
struct NucleicSessionAttributes: ActivityAttributes {
    /// The agent behind a session — drives a row's provider tag/tint.
    enum Backend: String, Codable, Hashable {
        case claude, codex, grok, other

        var label: String {
            switch self {
            case .claude: "Claude"
            case .codex: "Codex"
            case .grok: "Grok"
            case .other: "Agent"
            }
        }
    }

    /// A session's live state, collapsed to what the glance needs — drives each row's glyph/tint.
    enum Kind: String, Codable, Hashable {
        case running        // agent actively working a turn
        case provisioning   // worktree/setup spinning up
        case approval       // blocked on a human approval decision (loudest)
        case needsInput     // turn ended asking the user something
        case done           // turn finished its work; nothing required
        case error          // errored / interrupted
        case idle           // created, not yet working
    }

    /// One agent session surfaced in the activity's detail rows.
    struct SessionLine: Codable, Hashable, Identifiable {
        var id: String
        /// The session's title (falls back to the project name upstream).
        var title: String
        /// Project the session belongs to.
        var project: String
        var backend: Backend
        var kind: Kind
        /// A compact right-aligned status: its diff ("3 files +42 −7"), "2 to approve",
        /// "Waiting on you", "Working…", etc.
        var detail: String
        /// The single most-urgent pending approval on this session, when it has one — the id the
        /// inline Allow/Deny buttons resolve (`ApproveFromActivityIntent`). `nil` when the session
        /// isn't blocked on an approval, or when the producer doesn't carry it (an older host, or a
        /// summary that predates the field) — in which case the row falls back to a plain deep-link
        /// tap, exactly as before. Optional so a host that omits it still decodes.
        var approvalID: String?
        /// Whether that approval is high-risk (destructive / network / host-exec). The surface hides
        /// inline Allow on a high-risk request (§3.3) — only Deny / open-the-app is offered. `nil`
        /// (treated as unknown → no inline Allow) when the producer doesn't carry it.
        var approvalIsHighRisk: Bool?

        init(
            id: String, title: String, project: String, backend: Backend, kind: Kind,
            detail: String, approvalID: String? = nil, approvalIsHighRisk: Bool? = nil
        ) {
            self.id = id
            self.title = title
            self.project = project
            self.backend = backend
            self.kind = kind
            self.detail = detail
            self.approvalID = approvalID
            self.approvalIsHighRisk = approvalIsHighRisk
        }
    }

    struct ContentState: Codable, Hashable {
        /// Sessions actively working a turn.
        var runningCount: Int
        /// Sessions blocked on the user (approval or next prompt) — the loud number.
        var needsYouCount: Int
        /// Total individual approval decisions waiting across all sessions — the "act now" count.
        var approvalCount: Int
        /// Aggregate worktree churn across the active sessions.
        var filesChanged: Int
        var linesAdded: Int
        var linesRemoved: Int
        /// The most urgent sessions, attention-first (capped, typically ≤3): the detail rows.
        var lines: [SessionLine]

        /// The attention-bearing fingerprint of the glance: the headline counts plus each row's
        /// identity and `kind`. Deliberately EXCLUDES the mid-turn churn (files/±lines and the
        /// churn-derived detail), which ticks on every transcript edit while the agent works.
        /// `LiveActivityManager` gates its `activity.update` on this so the glance refreshes on a
        /// status change — a session starting, finishing, needing approval/input — not on every diff
        /// delta mid-turn (which would burn ActivityKit's update budget for an illegible number).
        /// Mirrors the host's `LiveActivitySnapshot.attentionSignature`.
        var attentionSignature: [String] {
            var parts = ["run:\(runningCount)", "need:\(needsYouCount)", "appr:\(approvalCount)"]
            parts.append(contentsOf: lines.map { "\($0.id):\($0.kind.rawValue)" })
            return parts
        }

        /// The single most urgent session (drives the compact/minimal regions).
        var top: SessionLine? { lines.first }
        /// Whether anything needs the user (waiting session or a pending approval).
        var needsAttention: Bool { needsYouCount > 0 || approvalCount > 0 }
        /// Whether any worktree has uncommitted changes to summarize.
        var hasChurn: Bool { filesChanged > 0 }
    }

    /// The paired Mac's name (static for the Activity's lifetime).
    var hostName: String
}

extension NucleicSessionAttributes.ContentState {
    /// Where a tap on the whole activity should go (UX_IOS §5.3): a session waiting on the user
    /// opens straight into that session; otherwise (work running, nothing waiting) the sessions
    /// list. `lines` is attention-first, so any waiting session is `lines.first` — and always in
    /// the capped list, since waiting outranks running in the sort.
    var tapURL: URL? {
        if let waiting = lines.first(where: { $0.kind == .approval || $0.kind == .needsInput }) {
            return NucleicDeepLink.session(waiting.id)
        }
        return NucleicDeepLink.sessionsList
    }
}

/// The `nucleic://` deep links the Live Activity emits and the app consumes. Kept in the shared
/// file so the widget that *builds* a URL and the app that *parses* one can't drift.
enum NucleicDeepLink {
    static let scheme = "nucleic"

    /// Open a specific session's detail: `nucleic://session/<id>`.
    static func session(_ id: String) -> URL? {
        var c = URLComponents()
        c.scheme = scheme
        c.host = "session"
        c.path = "/\(id)"
        return c.url
    }

    /// Surface the sessions list, no specific session: `nucleic://sessions`.
    static var sessionsList: URL? { URL(string: "\(scheme)://sessions") }

    /// What an incoming link asks the app to do.
    enum Route: Equatable {
        case session(String)
        case sessionsList
    }

    static func route(for url: URL) -> Route? {
        guard url.scheme == scheme else { return nil }
        switch url.host {
        case "session":
            let id = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
            return id.isEmpty ? nil : .session(id)
        case "sessions":
            return .sessionsList
        default:
            return nil
        }
    }
}
