//
//  MiniPlayerView.swift
//  Yattee
//
//  Mini player bar shown at bottom of screen during playback.
//

import SwiftUI
import NukeUI

#if os(iOS) || os(macOS)
struct MiniPlayerView: View {
    @Environment(\.appEnvironment) private var appEnvironment

    /// Whether this is displayed as a tab bottom accessory (affects styling)
    var isTabAccessory: Bool = false

    @State private var buttonTapCounts: [UUID: Int] = [:]
    @State private var miniPlayerSettings: MiniPlayerSettings = MiniPlayerSettings.cached

    private var playerService: PlayerService? { appEnvironment?.playerService }
    private var playerState: PlayerState? { playerService?.state }
    private var navigationCoordinator: NavigationCoordinator? { appEnvironment?.navigationCoordinator }
    private var deArrowProvider: DeArrowBrandingProvider? { appEnvironment?.deArrowBrandingProvider }
    private var queueManager: QueueManager? { appEnvironment?.queueManager }
    private var playerControlsLayoutService: PlayerControlsLayoutService? { appEnvironment?.playerControlsLayoutService }
    private var settingsManager: SettingsManager? { appEnvironment?.settingsManager }

    private var currentVideo: Video? { playerState?.currentVideo }

    /// Whether PiP is currently active
    private var isPiPActive: Bool { playerState?.pipState == .active }

    /// Whether video preview should be shown (video ready and player not expanded/expanding)
    private var shouldShowVideoPreview: Bool {
        guard let state = playerState else { return false }
        guard let nav = navigationCoordinator else { return false }
        // Check if video preview is enabled in settings
        guard miniPlayerSettings.showVideo else { return false }
        // Don't show video preview for audio-only streams - keep thumbnail visible
        let isAudioOnly = state.currentStream?.isAudioOnly == true
        guard !isAudioOnly else { return false }
        let isVideoReady = state.isFirstFrameReady && state.isBufferReady
        // Don't show video preview when player is expanded or expanding (but OK during collapse)
        // This prevents stealing the MPV player view from the expanded player during expand
        let isPlayerActive = nav.isPlayerExpanded || nav.isPlayerExpanding
        return isVideoReady && !isPlayerActive
    }

    /// Whether we should include the MPVRenderView in the hierarchy (even if hidden)
    /// During collapse, we want the view mounted so it can receive the player view immediately
    private var shouldMountVideoView: Bool {
        guard playerService?.currentBackend is MPVBackend else { return false }
        guard let nav = navigationCoordinator else { return false }
        // Don't mount video view if setting is disabled
        guard miniPlayerSettings.showVideo else { return false }
        // Mount when: not expanding AND (collapsing OR should show preview)
        // This ensures the container exists during collapse to receive the player view
        return !nav.isPlayerExpanding && (nav.isPlayerCollapsing || shouldShowVideoPreview)
    }

    /// The title to display, preferring DeArrow title if available.
    private var displayTitle: String {
        if let video = currentVideo, let deArrowTitle = deArrowProvider?.title(for: video) {
            return deArrowTitle
        }
        return currentVideo?.title ?? String(localized: "player.notPlaying")
    }

    /// Thumbnail URLs to try in order, preferring DeArrow, then the quality chain.
    private var displayThumbnailURLs: [URL] {
        guard let video = currentVideo else { return [] }
        let deArrowThumbnail = deArrowProvider?.thumbnailURL(for: video)
        return [deArrowThumbnail].compactMap { $0 } + video.thumbnailURLsByQuality
    }

    // MARK: - Actions

    /// Handle tap on video preview - action based on settings (PiP or expand)
    /// When video is disabled, always expand player since PiP needs the video view mounted
    private func handleVideoPreviewTap() {
        // Don't start PiP while the expanded player is open (or opening) — PiP
        // would play alongside the player window. Bring the window forward instead.
        if let nav = navigationCoordinator, nav.isPlayerExpanded || nav.isPlayerExpanding {
            nav.expandPlayer()
            return
        }

        // If video is disabled in mini player, always expand (can't start PiP without video view)
        guard miniPlayerSettings.showVideo else {
            navigationCoordinator?.expandPlayer()
            return
        }

        switch miniPlayerSettings.videoTapAction {
        case .startPiP:
            if let backend = playerService?.currentBackend as? MPVBackend {
                backend.startPiP()
            } else {
                navigationCoordinator?.expandPlayer()
            }
        case .expandPlayer:
            navigationCoordinator?.expandPlayer()
        }
    }

    /// Closes the current video: stops playback, clears the queue, and dismisses
    /// the expanded player (so the separate macOS player window is hidden too).
    private func closeVideo() {
        playerState?.isClosingVideo = true
        queueManager?.clearQueue()
        // Stop the player before dismissing the window so the backend is torn down first.
        playerService?.stop()
        navigationCoordinator?.isPlayerExpanded = false
    }

    /// Expand player, restoring from PiP first if needed
    /// If PiP is active, stops PiP and waits for it to close before expanding
    private func expandPlayerWithPiPRestore() {
        if let backend = playerService?.currentBackend as? MPVBackend, backend.isPiPActive {
            // Stop PiP first, wait for it to close, then expand
            backend.stopPiP()
            Task { @MainActor in
                try? await Task.sleep(for: .milliseconds(400))
                navigationCoordinator?.expandPlayer()
            }
        } else {
            navigationCoordinator?.expandPlayer()
        }
    }

    var body: some View {
        Group {
            #if os(macOS)
            macOSCapsuleLayout
            #else
            if isTabAccessory {
                accessoryLayout
            } else {
                overlayLayout
            }
            #endif
        }
        .accessibilityIdentifier("player.miniPlayer")
        .accessibilityLabel("player.miniPlayer")
        .modifier(MiniPlayerContextMenuModifier(video: currentVideo) {
            closeVideo()
        })
        .task {
            await loadMiniPlayerSettings()
        }
        .onReceive(NotificationCenter.default.publisher(for: .playerControlsActivePresetDidChange)) { _ in
            Task { await loadMiniPlayerSettings() }
        }
        .onReceive(NotificationCenter.default.publisher(for: .playerControlsPresetsDidChange)) { _ in
            Task { await loadMiniPlayerSettings() }
        }
    }

    /// Loads mini player settings from the active preset.
    private func loadMiniPlayerSettings() async {
        guard let layoutService = playerControlsLayoutService else { return }
        let layout = await layoutService.activeLayout()
        await MainActor.run {
            miniPlayerSettings = layout.effectiveMiniPlayerSettings
            MiniPlayerSettings.cached = layout.effectiveMiniPlayerSettings
            GlobalLayoutSettings.cached = layout.globalSettings
        }
    }

    // MARK: - Tab Accessory Layout (iOS 26+)

    private var accessoryLayout: some View {
        HStack(spacing: 8) {
            // Video preview - tap for PiP (or expand if PiP unavailable)
            videoPreviewView
                .frame(width: 64, height: 36)
                .clipShape(RoundedRectangle(cornerRadius: 6))
                .contentShape(Rectangle())
                .onTapGesture {
                    handleVideoPreviewTap()
                }

            // Title/author - tap to expand player (restores from PiP first if needed)
            Button {
                expandPlayerWithPiPRestore()
            } label: {
                VStack(alignment: .leading, spacing: 2) {
                    MarqueeText(
                        text: displayTitle,
                        font: .subheadline,
                        fontWeight: .medium,
                        foregroundStyle: .primary
                    )

                    if let authorName = currentVideo?.author.name, !authorName.isEmpty {
                        MarqueeText(
                            text: authorName,
                            font: .caption,
                            foregroundStyle: .secondary
                        )
                    }
                }
                .contentShape(Rectangle())
            }
            .buttonStyle(.plain)

            // Dynamic buttons from settings
            buttonsView
        }
        .padding(.leading, 12)
        .padding(.trailing, 8)
        .padding(.vertical, 8)
    }

    // MARK: - Overlay Layout

    private var overlayLayout: some View {
        HStack(spacing: 12) {
            // Video preview - tap for PiP (or expand if PiP unavailable)
            videoPreviewView
                .frame(width: 60, height: 34)
                .clipShape(RoundedRectangle(cornerRadius: 4))
                .contentShape(Rectangle())
                .onTapGesture {
                    handleVideoPreviewTap()
                }

            // Title/author area - tap to expand player (restores from PiP first if needed)
            VStack(alignment: .leading, spacing: 2) {
                MarqueeText(
                    text: displayTitle,
                    font: .subheadline,
                    fontWeight: .medium,
                    foregroundStyle: .primary
                )

                authorLine(font: .caption)
            }
            .contentShape(Rectangle())
            .onTapGesture {
                expandPlayerWithPiPRestore()
            }

            Spacer()
                .contentShape(Rectangle())
                .onTapGesture {
                    expandPlayerWithPiPRestore()
                }

            // Dynamic buttons from settings
            buttonsView

            // Always show close button in overlay layout
            closeButton
        }
        .padding(.horizontal)
        .padding(.vertical, 8)
        .background(.ultraThinMaterial)
        .overlay(alignment: .bottom) {
            progressLine
        }
    }

    // MARK: - macOS Capsule Layout

    #if os(macOS)
    /// Compact, centered capsule shown in the main window on macOS (Music.app style).
    /// The expanded player lives in a separate window, so this stays visible alongside it.
    private var macOSCapsuleLayout: some View {
        HStack(spacing: 10) {
            // Video preview - tap for PiP (or expand if PiP unavailable)
            videoPreviewView
                .frame(width: 44, height: 26)
                .clipShape(RoundedRectangle(cornerRadius: 4))
                .contentShape(Rectangle())
                .onTapGesture {
                    handleVideoPreviewTap()
                }

            // Title/author area - tap to expand player (restores from PiP first if needed)
            VStack(alignment: .leading, spacing: 1) {
                MarqueeText(
                    text: displayTitle,
                    font: .subheadline,
                    fontWeight: .medium,
                    foregroundStyle: .primary
                )

                authorLine(font: .caption)
            }
            .contentShape(Rectangle())
            .onTapGesture {
                expandPlayerWithPiPRestore()
            }

            // Dynamic buttons from settings (includes close by default - no separate close button)
            buttonsView
        }
        .padding(.horizontal, 12)
        .padding(.vertical, 6)
        .frame(maxWidth: 420)
        .glassBackground(.regular, in: .capsule, fallback: .regularMaterial)
        .overlay(alignment: .bottom) {
            progressLine
        }
        .clipShape(Capsule())
        .shadow(color: .black.opacity(0.25), radius: 8, y: 4)
    }
    #endif

    // MARK: - Shared Components

    /// Author line with a red LIVE indicator for live streams.
    /// MarqueeText draws its text at the top of a fixed-height frame, so the badge
    /// is top-aligned with it (dot stays centered against the LIVE text only).
    @ViewBuilder
    private func authorLine(font: Font) -> some View {
        let authorName = currentVideo?.author.name ?? ""
        if playerState?.isLive == true {
            HStack(alignment: .top, spacing: 4) {
                HStack(spacing: 4) {
                    Circle()
                        .fill(.red)
                        .frame(width: 6, height: 6)
                    Text(String(localized: "player.live"))
                        .font(font.weight(.semibold))
                        .foregroundStyle(.red)
                }
                .fixedSize()

                if !authorName.isEmpty {
                    MarqueeText(
                        text: authorName,
                        font: font,
                        foregroundStyle: .secondary
                    )
                }
            }
        } else if !authorName.isEmpty {
            MarqueeText(
                text: authorName,
                font: font,
                foregroundStyle: .secondary
            )
        }
    }

    /// Bottom progress line; hidden for live streams where progress is meaningless.
    @ViewBuilder
    private var progressLine: some View {
        if playerState?.isLive != true {
            GeometryReader { geo in
                Rectangle()
                    .fill(.red)
                    .frame(width: geo.size.width * (playerState?.progress ?? 0), height: 2)
            }
            .frame(height: 2)
        }
    }

    /// Video preview that shows live video when available, or thumbnail as fallback.
    @ViewBuilder
    private var videoPreviewView: some View {
        ZStack {
            // Video layer - mounted during collapse or when ready to show
            // Keep it in hierarchy during collapse so the container can receive the player view
            if let backend = playerService?.currentBackend as? MPVBackend,
               let playerState,
               shouldMountVideoView {
                MPVRenderViewRepresentable(backend: backend, playerState: playerState)
                    .allowsHitTesting(false)
            }

            // Thumbnail layer - on top so it can cover the black MPV view during PiP
            // Shown when video not ready, during expand animation, or during PiP
            thumbnailView
                .opacity(shouldShowVideoPreview && !isPiPActive ? 0 : 1)
                .animation(.easeInOut(duration: 0.15), value: shouldShowVideoPreview)
                .animation(.easeInOut(duration: 0.25), value: isPiPActive)
                .allowsHitTesting(false)
        }
        .onChange(of: navigationCoordinator?.isPlayerCollapsing) { _, isCollapsing in
            // When collapse animation finishes, resume rendering if video preview should be shown
            // This must happen AFTER playerSheetDidDisappear pauses rendering
            if isCollapsing == false, shouldShowVideoPreview,
               let backend = playerService?.currentBackend as? MPVBackend {
                backend.resumeRendering()
            }
        }
        .onChange(of: shouldShowVideoPreview) { _, showPreview in
            // Resume/pause rendering based on whether mini player video is visible
            // Only act when not in the middle of collapse animation
            if navigationCoordinator?.isPlayerCollapsing == false {
                if let backend = playerService?.currentBackend as? MPVBackend {
                    if showPreview {
                        backend.resumeRendering()
                    } else {
                        backend.pauseRendering()
                    }
                }
            }
        }
    }

    @ViewBuilder
    private var thumbnailView: some View {
        FallbackLazyImage(urls: displayThumbnailURLs) { state in
            if let image = state.image {
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } else {
                Rectangle()
                    .fill(.quaternary)
                    .overlay {
                        Image(systemName: "play.rectangle")
                            .foregroundStyle(.secondary)
                    }
            }
        }
    }

    // MARK: - Dynamic Buttons

    /// Renders buttons from miniPlayerSettings.buttons
    @ViewBuilder
    private var buttonsView: some View {
        ForEach(miniPlayerSettings.buttons) { config in
            miniPlayerButton(for: config)
        }
    }

    /// Renders a single button based on its configuration
    @ViewBuilder
    private func miniPlayerButton(for config: ControlButtonConfiguration) -> some View {
        switch config.buttonType {
        case .playPause:
            playPauseButton(config: config)
        case .playNext:
            playNextButton(config: config)
        case .playPrevious:
            playPreviousButton(config: config)
        case .seek:
            seekButton(config: config)
        case .close:
            closeButton
        case .queue:
            queueButton(config: config)
        case .share:
            shareButton(config: config)
        case .addToPlaylist:
            addToPlaylistButton(config: config)
        case .airplay:
            airplayButton
        case .pictureInPicture:
            pipButton(config: config)
        case .playbackSpeed:
            playbackSpeedButton(config: config)
        case .audioMode:
            audioModeButton(config: config)
        default:
            EmptyView()
        }
    }

    // MARK: - Individual Button Views

    private func playPauseButton(config: ControlButtonConfiguration) -> some View {
        Button {
            incrementTapCount(for: config)
            playerService?.togglePlayPause()
        } label: {
            Image(systemName: playPauseIcon)
                .font(isTabAccessory ? .title3 : .title2)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
                .contentTransition(.symbolEffect(.replace, options: .speed(2)))
        }
        .buttonStyle(.plain)
        .disabled(isTransportDisabled)
    }

    private func playNextButton(config: ControlButtonConfiguration) -> some View {
        Button {
            incrementTapCount(for: config)
            Task { await playerService?.playNext() }
        } label: {
            Image(systemName: "forward.fill")
                .symbolEffect(.bounce.down.byLayer, options: .nonRepeating, value: buttonTapCounts[config.id] ?? 0)
                .font(isTabAccessory ? .title3 : .title2)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
                .foregroundStyle(playerState?.hasNext == true ? .primary : .tertiary)
        }
        .buttonStyle(.plain)
        .disabled(playerState?.hasNext != true)
    }

    private func playPreviousButton(config: ControlButtonConfiguration) -> some View {
        Button {
            incrementTapCount(for: config)
            Task { await playerService?.playPrevious() }
        } label: {
            Image(systemName: "backward.fill")
                .symbolEffect(.bounce.down.byLayer, options: .nonRepeating, value: buttonTapCounts[config.id] ?? 0)
                .font(isTabAccessory ? .title3 : .title2)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
                .foregroundStyle(playerState?.hasPrevious == true ? .primary : .tertiary)
        }
        .buttonStyle(.plain)
        .disabled(playerState?.hasPrevious != true)
    }

    private func seekButton(config: ControlButtonConfiguration) -> some View {
        let settings = config.seekSettings ?? SeekSettings()
        return Button {
            incrementTapCount(for: config)
            playerService?.seek(seconds: Double(settings.seconds), direction: settings.direction)
        } label: {
            Image(systemName: settings.systemImage)
                .symbolEffect(.bounce.down.byLayer, options: .nonRepeating, value: buttonTapCounts[config.id] ?? 0)
                .font(isTabAccessory ? .title3 : .title2)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
    }

    private var closeButton: some View {
        Button {
            closeVideo()
        } label: {
            Image(systemName: "xmark")
                .font(.subheadline)
                .foregroundStyle(.secondary)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
    }

    private func queueButton(config: ControlButtonConfiguration) -> some View {
        Button {
            incrementTapCount(for: config)
            navigationCoordinator?.isMiniPlayerQueueSheetPresented = true
        } label: {
            ZStack(alignment: .bottom) {
                Image(systemName: "list.bullet")
                    .font(isTabAccessory ? .title3 : .title2)
                    .frame(width: 32, height: 32)

                // Badge showing queue count
                let queueCount = playerState?.queue.count ?? 0
                if queueCount > 0 {
                    Text("\(queueCount)")
                        .font(.system(size: 9, weight: .bold))
                        .foregroundStyle(.white)
                        .padding(.horizontal, 4)
                        .padding(.vertical, 1)
                        .background(Color.accentColor, in: Capsule())
                        .offset(y: 4)
                        .allowsHitTesting(false)
                }
            }
            .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
    }

    @ViewBuilder
    private func shareButton(config: ControlButtonConfiguration) -> some View {
        if let video = currentVideo {
            ShareLink(item: video.shareURL) {
                Image(systemName: "square.and.arrow.up")
                    .font(isTabAccessory ? .title3 : .title2)
                    .frame(width: 32, height: 32)
                    .contentShape(Rectangle())
            }
            .buttonStyle(.plain)
        }
    }

    private func addToPlaylistButton(config: ControlButtonConfiguration) -> some View {
        Button {
            incrementTapCount(for: config)
            navigationCoordinator?.isMiniPlayerPlaylistSheetPresented = true
        } label: {
            Image(systemName: "text.badge.plus")
                .font(isTabAccessory ? .title3 : .title2)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
    }

    @ViewBuilder
    private var airplayButton: some View {
        #if os(iOS)
        AirPlayButton(tintColor: .label)
            .frame(width: 32, height: 32)
        #elseif os(macOS)
        AirPlayButton()
            .frame(width: 32, height: 32)
        #endif
    }

    private func pipButton(config: ControlButtonConfiguration) -> some View {
        Button {
            incrementTapCount(for: config)
            // Don't start PiP while the expanded player is open (or opening) — PiP
            // would play alongside the player window. Bring the window forward instead.
            if let nav = navigationCoordinator, nav.isPlayerExpanded || nav.isPlayerExpanding {
                nav.expandPlayer()
                return
            }
            if let backend = playerService?.currentBackend as? MPVBackend {
                backend.startPiP()
            }
        } label: {
            Image(systemName: "pip.enter")
                .font(isTabAccessory ? .title3 : .title2)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
    }

    private func playbackSpeedButton(config: ControlButtonConfiguration) -> some View {
        let currentRate = playerState?.rate ?? .x1
        return Menu {
            ForEach(PlaybackRate.allCases) { rate in
                Button {
                    playerState?.rate = rate
                    playerService?.currentBackend?.rate = Float(rate.rawValue)
                } label: {
                    HStack {
                        Text(rate.displayText)
                        if currentRate == rate {
                            Spacer()
                            Image(systemName: "checkmark")
                        }
                    }
                }
            }
        } label: {
            Text(currentRate.compactDisplayText)
                .font(.system(size: 12, weight: .semibold))
                .foregroundStyle(.primary)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
        }
        .menuIndicator(.hidden)
        .tint(.primary)
    }

    private func audioModeButton(config: ControlButtonConfiguration) -> some View {
        let isAudioMode = appEnvironment?.settingsManager.audioOnlyModeEnabled ?? false
        return Button {
            incrementTapCount(for: config)
            Task { await playerService?.setAudioMode(!isAudioMode) }
        } label: {
            Image(systemName: "music.note")
                .font(isTabAccessory ? .title3 : .title2)
                .frame(width: 32, height: 32)
                .contentShape(Rectangle())
                .foregroundStyle(isAudioMode ? AnyShapeStyle(Color.red) : AnyShapeStyle(.primary))
        }
        .buttonStyle(.plain)
    }

    // MARK: - Helpers

    /// Whether transport controls should be disabled (during loading/buffering or buffer not ready)
    private var isTransportDisabled: Bool {
        guard let state = playerState else { return true }
        return state.playbackState == .loading ||
               state.playbackState == .buffering ||
               !state.isFirstFrameReady ||
               !state.isBufferReady
    }

    private var playPauseIcon: String {
        switch playerState?.playbackState {
        case .playing:
            return "pause.fill"
        default:
            return "play.fill"
        }
    }

    private func incrementTapCount(for config: ControlButtonConfiguration) {
        buttonTapCounts[config.id, default: 0] += 1
    }
}

// MARK: - Context Menu Modifier

/// A view modifier that conditionally applies the video context menu.
/// Only applies the context menu when a video is available.
private struct MiniPlayerContextMenuModifier: ViewModifier {
    let video: Video?
    let closeAction: () -> Void

    func body(content: Content) -> some View {
        if let video {
            content.videoContextMenu(
                video: video,
                customActions: [
                    VideoContextAction(
                        String(localized: "player.close"),
                        systemImage: "xmark",
                        role: .destructive,
                        action: closeAction
                    )
                ],
                context: .player
            )
        } else {
            content
        }
    }
}

// MARK: - Previews

#Preview("Overlay Mode") {
    VStack {
        Spacer()
        MiniPlayerView(isTabAccessory: false)
    }
    .appEnvironment(.preview)
}

#Preview("Accessory Mode") {
    MiniPlayerView(isTabAccessory: true)
        .appEnvironment(.preview)
}
#endif
