import CoreLocation
import Foundation
import MapKit

/// Captures the "where am I, what time is it" snapshot — used both when a
/// note is taken (stamped into the note) and when notes are recalled (fed to
/// the relevance ranking). Location is best-effort: denied or unavailable
/// location still yields a usable context (time zone, device).
@Observable
@MainActor
final class ContextService {
    /// Latest context snapshot. Updated by `refresh()`.
    private(set) var current: CaptureContext = .empty

    /// Raw location behind `current`, for distance math at recall time.
    private(set) var location: CLLocation?

    private(set) var isRefreshing = false
    private(set) var authorizationDenied = false

    init() {
        current.device = Self.deviceName
        current.timeZoneID = TimeZone.current.identifier
    }

    static var deviceName: String {
        #if os(macOS)
        "Mac"
        #else
        "iPhone"
        #endif
    }

    /// One-shot refresh: grab a current location (asking for when-in-use
    /// authorization if needed), then reverse-geocode it into place names.
    /// Bounded — gives up after ~10 seconds rather than spinning.
    func refresh() async {
        guard !isRefreshing else { return }
        isRefreshing = true
        defer { isRefreshing = false }

        current.device = Self.deviceName
        current.timeZoneID = TimeZone.current.identifier

        guard let location = await acquireLocation() else { return }
        self.location = location
        current.latitude = location.coordinate.latitude
        current.longitude = location.coordinate.longitude
        current.horizontalAccuracy = location.horizontalAccuracy

        await reverseGeocode(location)
    }

    /// First fix from `CLLocationUpdate.liveUpdates()`, raced against a
    /// 10-second deadline. `CLServiceSession` holds the when-in-use
    /// authorization request open for the duration.
    private func acquireLocation() async -> CLLocation? {
        // CLServiceSession is unavailable on macOS; there liveUpdates itself
        // triggers the authorization prompt.
        #if os(iOS)
        let session = CLServiceSession(authorization: .whenInUse)
        defer { session.invalidate() }
        #endif

        let fix = Task {
            for try await update in CLLocationUpdate.liveUpdates() {
                if update.authorizationDenied || update.authorizationDeniedGlobally {
                    await MainActor.run { self.authorizationDenied = true }
                    return nil as CLLocation?
                }
                if let location = update.location {
                    return location
                }
            }
            return nil
        }
        let deadline = Task {
            try? await Task.sleep(for: .seconds(10))
            fix.cancel()
        }
        defer { deadline.cancel() }

        let location = (try? await fix.value) ?? nil
        if location != nil { authorizationDenied = false }
        return location
    }

    private func reverseGeocode(_ location: CLLocation) async {
        guard let request = MKReverseGeocodingRequest(location: location) else { return }
        guard let item = try? await request.mapItems.first else { return }

        current.placeName = item.name
        current.thoroughfare = item.address?.shortAddress
        current.locality = item.addressRepresentations?.cityName
    }
}
