//
//  TopShelfSnapshotWriter.swift
//  Yattee
//
//  Builds Top Shelf snapshots from app data and persists them to the App Group
//  UserDefaults suite so the tvOS Top Shelf extension can read them without
//  touching SwiftData. No-op on non-tvOS platforms.
//

import Foundation
import SwiftData

@MainActor
enum TopShelfSnapshotWriter {
    #if os(tvOS)
    private static var observers: [NSObjectProtocol] = []
    #endif

    /// Re-writes all three snapshot sections from current app data.
    /// Call on app launch, after bookmark/watch mutations, and after feed refresh.
    static func writeAll(dataManager: DataManager?, settingsManager: SettingsManager? = nil) {
        #if os(tvOS)
        writeContinueWatching(dataManager: dataManager)
        writeBookmarks(dataManager: dataManager)
        writeFeed()
        if let settingsManager {
            settingsManager.mirrorEnabledSectionsToAppGroup(settingsManager.topShelfSections)
        }
        #endif
    }

    /// Registers NotificationCenter observers so bookmark/history changes
    /// elsewhere in the app keep the snapshot in sync. Idempotent.
    static func startObserving(dataManager: DataManager?) {
        #if os(tvOS)
        guard observers.isEmpty, let dataManager else { return }
        let center = NotificationCenter.default
        let boxed = WeakDataManagerBox(dataManager)
        observers.append(center.addObserver(
            forName: .bookmarksDidChange, object: nil, queue: .main
        ) { _ in
            Task { @MainActor in writeBookmarks(dataManager: boxed.value) }
        })
        observers.append(center.addObserver(
            forName: .watchHistoryDidChange, object: nil, queue: .main
        ) { _ in
            Task { @MainActor in writeContinueWatching(dataManager: boxed.value) }
        })
        #endif
    }

    static func writeContinueWatching(dataManager: DataManager?) {
        #if os(tvOS)
        guard let dataManager else { return }
        let history = dataManager.watchHistory(limit: 50)
        let items = history
            .filter { !$0.isFinished && !$0.isLive && $0.watchedSeconds > 10 }
            .prefix(TopShelfSnapshot.maxItems)
            .compactMap(Self.makeItem(from:))
        TopShelfSnapshot.write(Array(items), section: .continueWatching)
        #endif
    }

    static func writeBookmarks(dataManager: DataManager?) {
        #if os(tvOS)
        guard let dataManager else { return }
        let bookmarks = dataManager.bookmarks(limit: TopShelfSnapshot.maxItems)
        let items = bookmarks.compactMap(Self.makeItem(from:))
        TopShelfSnapshot.write(items, section: .recentBookmarks)
        #endif
    }

    static func writeFeed() {
        #if os(tvOS)
        let videos = SubscriptionFeedCache.shared.videos.prefix(TopShelfSnapshot.maxItems)
        let items = videos.compactMap(Self.makeItem(from:))
        TopShelfSnapshot.write(items, section: .recentFeed)
        #endif
    }
}

#if os(tvOS)
/// Wraps a weak DataManager reference so it can be captured in Sendable closures.
/// Access is confined to the main actor via the enclosing NotificationCenter queue.
private final class WeakDataManagerBox: @unchecked Sendable {
    weak var value: DataManager?
    init(_ value: DataManager?) { self.value = value }
}

private extension TopShelfSnapshotWriter {
    static func makeItem(from bookmark: Bookmark) -> TopShelfItem? {
        guard let deepLink = deepLinkURL(
            videoID: bookmark.videoID,
            sourceRawValue: bookmark.sourceRawValue,
            globalProvider: bookmark.globalProvider,
            instanceURLString: bookmark.instanceURLString
        ) else { return nil }
        return TopShelfItem(
            videoID: bookmark.videoID,
            title: bookmark.title,
            authorName: bookmark.authorName,
            duration: bookmark.duration,
            thumbnailURL: reliableThumbnailURLString(bookmark.thumbnailURLString),
            deepLinkURL: deepLink,
            progressSeconds: nil
        )
    }

    static func makeItem(from entry: WatchEntry) -> TopShelfItem? {
        guard let deepLink = deepLinkURL(
            videoID: entry.videoID,
            sourceRawValue: entry.sourceRawValue,
            globalProvider: entry.globalProvider,
            instanceURLString: entry.instanceURLString
        ) else { return nil }
        return TopShelfItem(
            videoID: entry.videoID,
            title: entry.title,
            authorName: entry.authorName,
            duration: entry.duration,
            thumbnailURL: reliableThumbnailURLString(entry.thumbnailURLString),
            deepLinkURL: deepLink,
            progressSeconds: entry.watchedSeconds
        )
    }

    static func makeItem(from video: Video) -> TopShelfItem? {
        guard let deepLink = deepLinkURL(for: video.id) else { return nil }
        let thumbnail = bestThumbnailURL(from: video.thumbnails)
        return TopShelfItem(
            videoID: video.id.videoID,
            title: video.title,
            authorName: video.author.name,
            duration: video.duration,
            thumbnailURL: thumbnail,
            deepLinkURL: deepLink,
            progressSeconds: nil
        )
    }

    /// Builds a `yattee://video/...` deep link from a Video's ID.
    /// Returns nil for source types not round-trippable via the URL scheme (e.g. extracted).
    static func deepLinkURL(for videoID: VideoID) -> String? {
        switch videoID.source {
        case .global:
            return "yattee://video/\(videoID.videoID)"
        case .federated(_, let instance):
            var components = URLComponents()
            components.scheme = "yattee"
            components.host = "video"
            components.path = "/\(videoID.videoID)"
            components.queryItems = [
                URLQueryItem(name: "source", value: "peertube"),
                URLQueryItem(name: "instance", value: instance.absoluteString)
            ]
            return components.url?.absoluteString
        case .extracted:
            return nil
        }
    }

    /// Builds a deep link from the stored Bookmark/WatchEntry source fields.
    static func deepLinkURL(
        videoID: String,
        sourceRawValue: String,
        globalProvider: String?,
        instanceURLString: String?
    ) -> String? {
        switch sourceRawValue {
        case "global":
            // Only YouTube survives the round-trip; other global providers fall through.
            return "yattee://video/\(videoID)"
        case "federated":
            guard globalProvider == "peertube",
                  let urlStr = instanceURLString,
                  let instanceURL = URL(string: urlStr) else {
                return nil
            }
            var components = URLComponents()
            components.scheme = "yattee"
            components.host = "video"
            components.path = "/\(videoID)"
            components.queryItems = [
                URLQueryItem(name: "source", value: "peertube"),
                URLQueryItem(name: "instance", value: instanceURL.absoluteString)
            ]
            return components.url?.absoluteString
        default:
            return nil
        }
    }

    /// The Top Shelf extension sets a single image URL with no failure fallback,
    /// so rewrite to the always-available `hqdefault` variant: the best advertised
    /// variant is often `maxresdefault`, which 404s for many older videos and
    /// would leave an empty tile.
    static func bestThumbnailURL(from thumbnails: [Thumbnail]) -> String? {
        Thumbnail.reliableURL(for: thumbnails.max(by: { $0.quality < $1.quality })?.url)?.absoluteString
    }

    static func reliableThumbnailURLString(_ urlString: String?) -> String? {
        guard let urlString else { return nil }
        return Thumbnail.reliableURL(for: URL(string: urlString))?.absoluteString ?? urlString
    }
}
#endif
