//
//  VideoContextMenu.swift
//  Yattee
//
//  Shared context menu for video items.
//

import SwiftUI

// MARK: - Types

/// A custom action to display in the video context menu.
struct VideoContextAction {
    let label: String
    let systemImage: String
    let role: ButtonRole?
    let action: () -> Void

    init(_ label: String, systemImage: String, role: ButtonRole? = nil, action: @escaping () -> Void) {
        self.label = label
        self.systemImage = systemImage
        self.role = role
        self.action = action
    }
}

/// Context indicating which view the menu is being shown from.
/// Used to customize built-in menu items based on the current view.
enum VideoContextMenuContext {
    case `default`        // Standard behavior (show all items)
    case history          // Viewing history list
    case bookmarks        // Viewing bookmarks (hide bookmark toggle)
    case playlist         // Viewing a playlist
    case continueWatching // Continue watching section
    case downloads        // Viewing downloads (hide download option, bookmark toggle)
    case mediaBrowser     // Browsing media source files (hide bookmark toggle, playlist, download)
    case player           // In player view (hide play action - video already playing)
}

// MARK: - View Modifier

/// View modifier that attaches VideoContextMenu and its required sheets.
struct VideoContextMenuModifier: ViewModifier {
    @Environment(\.appEnvironment) private var appEnvironment

    let video: Video
    var customActions: [VideoContextAction] = []
    var context: VideoContextMenuContext = .default
    var startTime: Double? = nil
    var watchProgress: Double? = nil

    @State private var showingPlaylistSheet = false
    @State private var showingDownloadSheet = false
    @State private var showingDeleteDownloadConfirmation = false
    @State private var downloadToDelete: Download?
    @State private var menuRefreshID = UUID()

    func body(content: Content) -> some View {
        content
            #if os(tvOS)
            .contextMenu {
                VideoContextMenuContent(
                    video: video,
                    customActions: customActions,
                    context: context,
                    startTime: startTime,
                    showingPlaylistSheet: $showingPlaylistSheet,
                    showingDownloadSheet: $showingDownloadSheet,
                    showingDeleteDownloadConfirmation: $showingDeleteDownloadConfirmation,
                    downloadToDelete: $downloadToDelete,
                    appEnvironment: appEnvironment
                )
                .id(menuRefreshID)
            }
            #else
            .contextMenu(menuItems: {
                VideoContextMenuContent(
                    video: video,
                    customActions: customActions,
                    context: context,
                    startTime: startTime,
                    showingPlaylistSheet: $showingPlaylistSheet,
                    showingDownloadSheet: $showingDownloadSheet,
                    showingDeleteDownloadConfirmation: $showingDeleteDownloadConfirmation,
                    downloadToDelete: $downloadToDelete,
                    appEnvironment: appEnvironment
                )
                .id(menuRefreshID)
            }, preview: {
                VideoRowView(
                    video: video,
                    style: .regular,
                    watchProgress: watchProgress,
                    disableInternalTapHandling: true
                )
                .frame(width: 320)
                .padding()
                .environment(\.appEnvironment, appEnvironment)
            })
            #endif
            .sheet(isPresented: $showingPlaylistSheet) {
                PlaylistSelectorSheet(video: video)
            }
            #if !os(tvOS)
            .sheet(isPresented: $showingDownloadSheet) {
                DownloadQualitySheet(video: video)
            }
            .alert(String(localized: "videoInfo.download.remove.title"), isPresented: $showingDeleteDownloadConfirmation) {
                Button(String(localized: "common.cancel"), role: .cancel) {}
                Button(String(localized: "videoInfo.download.remove.confirm"), role: .destructive) {
                    if let download = downloadToDelete {
                        Task {
                            await appEnvironment?.downloadManager.delete(download)
                        }
                    }
                }
            } message: {
                Text(String(localized: "videoInfo.download.remove.message"))
            }
            #endif
            .onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in
                menuRefreshID = UUID()
            }
    }
}

// MARK: - Menu Content

/// The actual menu content (uses bindings from parent for sheet presentation).
/// All observable values are snapshotted at init time to prevent redraws during playback.
struct VideoContextMenuContent: View {
    @Environment(\.appEnvironment) private var appEnvironment

    let video: Video
    var customActions: [VideoContextAction] = []
    var context: VideoContextMenuContext = .default
    var startTime: Double? = nil
    @Binding var showingPlaylistSheet: Bool
    @Binding var showingDownloadSheet: Bool
    @Binding var showingDeleteDownloadConfirmation: Bool
    @Binding var downloadToDelete: Download?

    // MARK: - Snapshotted Values (captured at init to prevent observation)

    /// Snapshotted remote control enabled state.
    private let remoteControlEnabled: Bool
    /// Snapshotted discovered devices list.
    private let snapshotDevices: [DiscoveredDevice]
    /// Snapshotted queue enabled setting.
    private let queueEnabled: Bool
    /// Snapshotted bookmark state.
    private let isBookmarked: Bool
    /// Snapshotted downloading state.
    private let isDownloading: Bool
    /// Snapshotted active download.
    private let activeDownload: Download?
    /// Snapshotted downloaded state.
    private let isDownloaded: Bool
    /// Snapshotted download object for deletion.
    private let snapshotDownload: Download?
    /// Snapshotted state indicating if queue has items (video playing or queued).
    private let hasQueueItems: Bool

    // MARK: - Init

    init(
        video: Video,
        customActions: [VideoContextAction] = [],
        context: VideoContextMenuContext = .default,
        startTime: Double? = nil,
        showingPlaylistSheet: Binding<Bool>,
        showingDownloadSheet: Binding<Bool>,
        showingDeleteDownloadConfirmation: Binding<Bool> = .constant(false),
        downloadToDelete: Binding<Download?> = .constant(nil),
        appEnvironment: AppEnvironment? = nil
    ) {
        self.video = video
        self.customActions = customActions
        self.context = context
        self.startTime = startTime
        self._showingPlaylistSheet = showingPlaylistSheet
        self._showingDownloadSheet = showingDownloadSheet
        self._showingDeleteDownloadConfirmation = showingDeleteDownloadConfirmation
        self._downloadToDelete = downloadToDelete

        // Snapshot observable values to prevent view updates during playback
        self.remoteControlEnabled = appEnvironment?.remoteControlCoordinator.isEnabled ?? false
        self.snapshotDevices = appEnvironment?.remoteControlCoordinator.discoveredDevices ?? []
        self.queueEnabled = appEnvironment?.settingsManager.queueEnabled ?? true
        self.isBookmarked = appEnvironment?.dataManager.isBookmarked(videoID: video.id.videoID) ?? false
        self.isDownloading = appEnvironment?.downloadManager.isDownloading(video.id) ?? false
        self.activeDownload = appEnvironment?.downloadManager.download(for: video.id)
        self.isDownloaded = appEnvironment?.downloadManager.isDownloaded(video.id) ?? false
        // Snapshot download for potential deletion
        self.snapshotDownload = appEnvironment?.downloadManager.download(for: video.id)
        // Queue actions only make sense when there's already a video playing or queued
        let playerState = appEnvironment?.playerService.state
        self.hasQueueItems = playerState?.currentVideo != nil || playerState?.hasNext == true
    }

    // MARK: - Computed Properties (context-based, not observable)

    /// Whether to show the bookmark toggle based on context
    private var showBookmarkToggle: Bool {
        !video.isFromLocalFolder && context != .bookmarks && context != .downloads && context != .mediaBrowser
    }

    /// Whether to show add to playlist based on context
    private var showAddToPlaylist: Bool {
        !video.isFromLocalFolder && context != .mediaBrowser
    }

    /// Whether to show the download option based on context
    private var showDownloadOption: Bool {
        context != .downloads
    }

    /// Whether to show Go to channel based on context
    private var showGoToChannel: Bool {
        context != .mediaBrowser
    }

    /// Whether to show the play action based on context
    private var showPlayAction: Bool {
        context != .player
    }

    /// Whether to show queue actions based on context, settings, and queue state
    private var showQueueActions: Bool {
        context != .player && queueEnabled && hasQueueItems
    }

    /// Computed at render time to always show current watch state
    private var isWatched: Bool {
        appEnvironment?.dataManager.watchEntry(for: video.id.videoID)?.isFinished ?? false
    }

    @ViewBuilder
    private var playDownloadShareButtons: some View {
        // Play (hidden in player context since video is already playing)
        if showPlayAction {
            Button {
                if let startTime {
                    appEnvironment?.playerService.openVideo(video, startTime: startTime)
                } else {
                    appEnvironment?.playerService.openVideo(video)
                }
            } label: {
                Label(String(localized: "video.context.play"), systemImage: "play.fill")
            }
        }

        #if !os(tvOS)
        // Download / Cancel download / Downloaded
        if showDownloadOption {
            if isDownloading, let download = activeDownload {
                Button(role: .destructive) {
                    Task {
                        await appEnvironment?.downloadManager.cancel(download)
                    }
                } label: {
                    Label(String(localized: "video.context.cancelDownload"), systemImage: "xmark.circle")
                }
            } else if isDownloaded, let download = snapshotDownload {
                Button {
                    downloadToDelete = download
                    showingDeleteDownloadConfirmation = true
                } label: {
                    Label(String(localized: "video.context.downloaded"), systemImage: "checkmark.circle.fill")
                }
            } else {
                Button {
                    startDownload(for: video)
                } label: {
                    Label(String(localized: "video.context.download"), systemImage: "arrow.down.circle")
                }
            }
        }
        #endif

        // Share
        #if !os(tvOS)
        ShareLink(item: video.shareURL) {
            Label(String(localized: "video.context.share"), systemImage: "square.and.arrow.up")
        }
        #endif
    }

    var body: some View {
        // Custom actions at the top
        ForEach(customActions.indices, id: \.self) { index in
            let action = customActions[index]
            Button(role: action.role) {
                action.action()
            } label: {
                Label(action.label, systemImage: action.systemImage)
            }
        }

        // Divider after custom actions (if any)
        if !customActions.isEmpty {
            Divider()
        }
        
        #if os(iOS)
        ControlGroup {
            playDownloadShareButtons
        }
        #else
        playDownloadShareButtons
        #endif

        // Play from Beginning (only shown when there's saved progress)
        if showPlayAction, let startTime, startTime > 0 {
            Button {
                appEnvironment?.playerService.openVideo(video, startTime: 0)
            } label: {
                Label(String(localized: "video.context.playFromBeginning"), systemImage: "arrow.counterclockwise")
            }
        }

        // Mark as Watched / Unwatched
        if !video.isFromLocalFolder {
            Button {
                if isWatched {
                    appEnvironment?.dataManager.markAsUnwatched(videoID: video.id.videoID)
                } else {
                    appEnvironment?.dataManager.markAsWatched(video: video)
                }
            } label: {
                if isWatched {
                    Label(String(localized: "video.context.markUnwatched"), systemImage: "eye.slash")
                } else {
                    Label(String(localized: "video.context.markWatched"), systemImage: "eye")
                }
            }
        }

        // Queue actions (hidden in player context and when queue feature is disabled)
        if showQueueActions {
            Button {
                appEnvironment?.queueManager.playNext(video)
            } label: {
                Label(String(localized: "video.context.playNext"), systemImage: "text.line.first.and.arrowtriangle.forward")
            }

            Button {
                appEnvironment?.queueManager.addToQueue(video)
            } label: {
                Label(String(localized: "video.context.addToQueue"), systemImage: "text.append")
            }
        }

        // Play on remote devices (for non-player contexts - play video directly on remote device)
        if context != .player, remoteControlEnabled, !snapshotDevices.isEmpty {
            Divider()

            ForEach(snapshotDevices) { device in
                Button {
                    playOnRemoteDevice(device)
                } label: {
                    Label(
                        String(format: String(localized: "video.context.playOn %@"), device.name),
                        systemImage: device.platform.iconName
                    )
                }
            }
        }

        // Move to remote devices (only in player context where video is playing)
        if context == .player, remoteControlEnabled, !snapshotDevices.isEmpty {
            Divider()

            ForEach(snapshotDevices) { device in
                Button {
                    moveToRemoteDevice(device)
                } label: {
                    Label(
                        String(format: String(localized: "video.context.moveTo %@"), device.name),
                        systemImage: device.platform.iconName
                    )
                }
            }
        }

        Divider()

        // Video Info
        Button {
            // Dismiss player if in player context
            if context == .player {
                // Set collapsing first so mini player shows video immediately
                appEnvironment?.navigationCoordinator.isPlayerCollapsing = true
                appEnvironment?.navigationCoordinator.isPlayerExpanded = false
            }
            appEnvironment?.navigationCoordinator.navigate(to: .video(.loaded(video)))
        } label: {
            Label(String(localized: "video.context.info"), systemImage: "info.circle")
        }

        // Go to channel (hidden for media browser, or when no real channel info)
        if showGoToChannel && video.author.hasRealChannelInfo {
            Button {
                appEnvironment?.navigationCoordinator.navigateToChannel(for: video, collapsePlayer: context == .player)
            } label: {
                Label(String(localized: "video.context.goToChannel"), systemImage: "person.circle")
            }
        }

        // Add to bookmarks / Remove from bookmarks
        if showBookmarkToggle {
            Button {
                if isBookmarked {
                    appEnvironment?.dataManager.removeBookmark(for: video.id.videoID)
                } else {
                    appEnvironment?.dataManager.addBookmark(for: video)
                }
            } label: {
                if isBookmarked {
                    Label(String(localized: "video.context.removeFromBookmarks"), systemImage: "bookmark.slash")
                } else {
                    Label(String(localized: "video.context.addToBookmarks"), systemImage: "bookmark")
                }
            }
        }

        // Add to playlist
        if showAddToPlaylist {
            Button {
                showingPlaylistSheet = true
            } label: {
                Label(String(localized: "video.context.addToPlaylist"), systemImage: "text.badge.plus")
            }
        }
    }

    // MARK: - Download

    #if !os(tvOS)
    /// Starts a download either automatically or by showing the quality sheet.
    private func startDownload(for video: Video) {
        guard let appEnvironment else {
            showingDownloadSheet = true
            return
        }

        // Media source videos (SMB/WebDAV/local) use direct file URLs - no API call needed
        if video.isFromMediaSource {
            Task {
                do {
                    try await appEnvironment.downloadManager.autoEnqueueMediaSource(
                        video,
                        mediaSourcesManager: appEnvironment.mediaSourcesManager,
                        webDAVClient: appEnvironment.webDAVClient,
                        smbClient: appEnvironment.smbClient
                    )
                } catch {
                    appEnvironment.toastManager.show(
                        category: .error,
                        title: String(localized: "download.error.title"),
                        subtitle: error.localizedDescription,
                        icon: "exclamationmark.triangle",
                        iconColor: .red
                    )
                }
            }
            return
        }

        let downloadSettings = appEnvironment.downloadSettings

        // Check if auto-download mode
        if downloadSettings.preferredDownloadQuality != .ask,
           let instance = appEnvironment.instancesManager.instance(for: video) {
            Task {
                do {
                    try await appEnvironment.downloadManager.autoEnqueue(
                        video,
                        preferredQuality: downloadSettings.preferredDownloadQuality,
                        preferredAudioLanguage: appEnvironment.settingsManager.preferredAudioLanguage,
                        preferredSubtitlesLanguage: appEnvironment.settingsManager.preferredSubtitlesLanguage,
                        includeSubtitles: downloadSettings.includeSubtitlesInAutoDownload,
                        contentService: appEnvironment.contentService,
                        instance: instance
                    )
                } catch {
                    appEnvironment.toastManager.show(
                        category: .error,
                        title: String(localized: "download.error.title"),
                        subtitle: error.localizedDescription,
                        icon: "exclamationmark.triangle",
                        iconColor: .red
                    )
                }
            }
        } else {
            showingDownloadSheet = true
        }
    }
    #endif

    // MARK: - Remote Control

    /// Play a video on a remote device (from non-player context).
    /// Sends the video to play from the beginning on the remote device.
    private func playOnRemoteDevice(_ device: DiscoveredDevice) {
        LoggingService.shared.logRemoteControl("[VideoContextMenu] playOnRemoteDevice called for device: \(device.name)")
        guard let remoteControl = appEnvironment?.remoteControlCoordinator else {
            LoggingService.shared.logRemoteControlError("[VideoContextMenu] No remoteControlCoordinator available", error: nil)
            return
        }

        LoggingService.shared.logRemoteControl("[VideoContextMenu] Starting Task for remote playback")
        Task {
            LoggingService.shared.logRemoteControl("[VideoContextMenu] Task started, checking connection")
            // Connect to device if not already connected
            if !remoteControl.controllingDevices.contains(device.id) {
                LoggingService.shared.logRemoteControl("[VideoContextMenu] Connecting to device: \(device.name)")
                try? await remoteControl.connect(to: device)
            }

            // Get the appropriate instance URL for the video's content type
            let instanceURL = appEnvironment?.instancesManager.instance(for: video.id.source)?.url.absoluteString

            LoggingService.shared.logRemoteControl("[VideoContextMenu] Calling loadVideo on remoteControl")
            // Send load video command (starts from beginning, doesn't pause local since nothing is playing)
            await remoteControl.loadVideo(
                videoID: video.id.videoID,
                videoTitle: video.title,
                videoSource: video.id.source,
                instanceURL: instanceURL,
                startTime: video.isLive ? nil : startTime,
                pauseLocalPlayback: false,
                on: device
            )
            LoggingService.shared.logRemoteControl("[VideoContextMenu] loadVideo call completed")
        }
        LoggingService.shared.logRemoteControl("[VideoContextMenu] playOnRemoteDevice returning (Task launched)")
    }

    /// Move the currently playing video to a remote device.
    /// Sends the video with current playback time and pauses local playback when remote device starts playing.
    private func moveToRemoteDevice(_ device: DiscoveredDevice) {
        guard let remoteControl = appEnvironment?.remoteControlCoordinator else { return }

        // Get current playback time from player service
        let currentTime = appEnvironment?.playerService.state.currentTime ?? 0

        Task {
            // Connect to device if not already connected
            if !remoteControl.controllingDevices.contains(device.id) {
                try? await remoteControl.connect(to: device)
            }

            // Get the appropriate instance URL for the video's content type
            let instanceURL = appEnvironment?.instancesManager.instance(for: video.id.source)?.url.absoluteString

            // Send load video command with current playback time
            // pauseLocalPlayback: true will pause local playback when remote device confirms it started playing
            // Live streams have no shared timeline - the remote device joins the stream at the live edge
            await remoteControl.loadVideo(
                videoID: video.id.videoID,
                videoTitle: video.title,
                videoSource: video.id.source,
                instanceURL: instanceURL,
                startTime: video.isLive ? nil : currentTime,
                pauseLocalPlayback: true,
                on: device
            )
        }
    }
}

// MARK: - View Extension

extension View {
    /// Attaches a video context menu with optional custom actions and context.
    ///
    /// - Parameters:
    ///   - video: The video to show the context menu for.
    ///   - customActions: Custom actions to display at the top of the menu.
    ///   - context: The view context, used to customize built-in menu items.
    ///   - startTime: Optional start time in seconds for the Play action.
    ///   - watchProgress: Optional watch progress (0-1) for the preview thumbnail.
    func videoContextMenu(
        video: Video,
        customActions: [VideoContextAction] = [],
        context: VideoContextMenuContext = .default,
        startTime: Double? = nil,
        watchProgress: Double? = nil
    ) -> some View {
        modifier(VideoContextMenuModifier(
            video: video,
            customActions: customActions,
            context: context,
            startTime: startTime,
            watchProgress: watchProgress
        ))
    }
}

// MARK: - Dropdown Menu View

#if !os(tvOS)
/// A dropdown menu view for videos, showing context menu actions.
/// Used in player views where the video is already playing.
struct VideoContextMenuView: View {
    @Environment(\.appEnvironment) private var appEnvironment

    let video: Video
    let accentColor: Color
    var buttonSize: CGFloat = 32
    var buttonBackgroundStyle: ButtonBackgroundStyle = .none
    var theme: ControlsTheme = .dark

    @State private var showingPlaylistSheet = false
    @State private var showingDownloadSheet = false
    @State private var showingDeleteDownloadConfirmation = false
    @State private var downloadToDelete: Download?
    @State private var refreshID = UUID()

    private var frameSize: CGFloat {
        buttonBackgroundStyle.glassStyle != nil ? buttonSize * 1.15 : buttonSize
    }

    var body: some View {
        Menu {
            VideoContextMenuContent(
                video: video,
                context: .player,
                showingPlaylistSheet: $showingPlaylistSheet,
                showingDownloadSheet: $showingDownloadSheet,
                showingDeleteDownloadConfirmation: $showingDeleteDownloadConfirmation,
                downloadToDelete: $downloadToDelete,
                appEnvironment: appEnvironment
            )
        } label: {
            contextMenuLabel
        }
        .id(refreshID)
        .menuIndicator(.hidden)
        #if os(macOS)
        // The default macOS menu style renders as a bordered pull-down that
        // stretches to fill available width on macOS 15 (Sequoia).
        .menuStyle(.borderlessButton)
        .fixedSize()
        #endif
        .sheet(isPresented: $showingPlaylistSheet) {
            PlaylistSelectorSheet(video: video)
        }
        .sheet(isPresented: $showingDownloadSheet) {
            DownloadQualitySheet(video: video)
        }
        .alert(String(localized: "videoInfo.download.remove.title"), isPresented: $showingDeleteDownloadConfirmation) {
            Button(String(localized: "common.cancel"), role: .cancel) {}
            Button(String(localized: "videoInfo.download.remove.confirm"), role: .destructive) {
                if let download = downloadToDelete {
                    Task {
                        await appEnvironment?.downloadManager.delete(download)
                    }
                }
            }
        } message: {
            Text(String(localized: "videoInfo.download.remove.message"))
        }
        .onReceive(NotificationCenter.default.publisher(for: .bookmarksDidChange)) { _ in
            refreshID = UUID()
        }
        .onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in
            refreshID = UUID()
        }
    }

    @ViewBuilder
    private var contextMenuLabel: some View {
        if let glassStyle = buttonBackgroundStyle.glassStyle {
            Image(systemName: "ellipsis")
                .font(.title2)
                .foregroundStyle(accentColor)
                .frame(width: frameSize, height: frameSize)
                .glassBackground(glassStyle, in: .circle, fallback: .ultraThinMaterial, colorScheme: theme.colorScheme)
                .contentShape(Circle())
        } else {
            Image(systemName: "ellipsis")
                .font(.title3)
                .foregroundStyle(accentColor)
                .frame(width: buttonSize, height: buttonSize)
                .contentShape(Rectangle())
        }
    }
}
#endif
