//
//  UnifiedPlaylistDetailView.swift
//  Yattee
//
//  Unified view for displaying both local and remote playlists.
//

import SwiftUI
import NukeUI

/// Cached playlist data for showing header immediately while loading.
private struct CachedPlaylistHeader {
    let title: String
    let thumbnailURL: URL?
    let videoCount: Int

    init(from recentPlaylist: RecentPlaylist) {
        title = recentPlaylist.title
        thumbnailURL = recentPlaylist.thumbnailURLString.flatMap { URL(string: $0) }
        videoCount = recentPlaylist.videoCount
    }
}

/// Source type for playlist data - either local (SwiftData) or remote (API).
enum PlaylistSource: Hashable {
    case local(UUID, title: String? = nil)
    case remote(PlaylistID, instance: Instance?, title: String? = nil)

    /// Initial title to show while loading (if provided).
    var initialTitle: String? {
        switch self {
        case .local(_, let title): return title
        case .remote(_, _, let title): return title
        }
    }

    /// Returns a unique identifier for zoom transitions.
    var transitionID: AnyHashable {
        switch self {
        case .local(let uuid, _):
            return uuid
        case .remote(let playlistID, _, _):
            return playlistID
        }
    }
}

struct UnifiedPlaylistDetailView: View {
    @Environment(\.appEnvironment) private var appEnvironment
    @Environment(\.dismiss) private var dismiss

    let source: PlaylistSource

    // MARK: - Shared State

    @State private var title: String

    init(source: PlaylistSource) {
        self.source = source
        self._title = State(initialValue: source.initialTitle ?? "")
    }
    @State private var descriptionText: String?
    @State private var thumbnailURL: URL?
    @State private var videos: [Video] = []
    @State private var videoCount: Int = 0

    // MARK: - Remote-only State

    @State private var isLoading = true
    @State private var cachedHeader: CachedPlaylistHeader?
    @State private var errorMessage: String?
    @State private var isImporting = false
    @State private var importProgress: (current: Int, total: Int)?
    @State private var remotePlaylist: Playlist?

    // MARK: - Local-only State

    @State private var localPlaylist: LocalPlaylist?
    @State private var showingEditSheet = false
    @State private var showingDeleteConfirmation = false
    @State private var isDescriptionExpanded = false

    #if os(tvOS)
    @State private var isDescriptionScrollLocked = false
    #endif

    #if !os(tvOS)
    @State private var downloadCoordinator = BatchDownloadCoordinator()
    // Cache download state to avoid triggering @Observable tracking on every render.
    // Prevents entire playlist view re-rendering when download progress updates.
    @State private var cachedAllVideosDownloaded = false
    @State private var hasLoadedDownloadState = false
    #endif

    @State private var watchEntriesMap: [String: WatchEntry] = [:]

    private var dataManager: DataManager? { appEnvironment?.dataManager }
    private var isQueueEnabled: Bool { appEnvironment?.settingsManager.queueEnabled ?? true }

    private var isLocal: Bool {
        if case .local = source { return true }
        return false
    }

    private var navigationTitleText: String {
        title.isEmpty ? cachedHeader?.title ?? String(localized: "playlist.title") : title
    }

    private var navigationThumbnailURL: URL? {
        // Rendered as a single URL without fallback, so rewrite to the
        // always-available hqdefault variant.
        Thumbnail.reliableURL(for: videos.first?.bestThumbnail?.url ?? thumbnailURL ?? cachedHeader?.thumbnailURL)
    }

    /// Summary text for the playlist (e.g., "5 videos · 1h 23m").
    private var playlistSummaryText: String? {
        var parts: [String] = []

        if videoCount > 0 || !isLoading {
            parts.append(String(localized: "playlist.videoCount \(videoCount)"))
        }

        if let localPlaylist {
            parts.append(localPlaylist.formattedTotalDuration)
        }

        // Return placeholder while loading to prevent navigation subtitle jump
        if parts.isEmpty && isLoading {
            return String(localized: "common.loading")
        }

        return parts.isEmpty ? nil : parts.joined(separator: " · ")
    }

    // MARK: - Body

    var body: some View {
        Group {
            if !title.isEmpty || localPlaylist != nil {
                playlistContent
            } else if case .remote = source, isLoading, let cachedHeader {
                // Show header with cached data + spinner for video list
                loadingContent(cachedHeader)
            } else if case .remote = source, isLoading {
                ProgressView()
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            } else if let error = errorMessage {
                errorView(error)
            } else {
                ContentUnavailableView(
                    String(localized: "playlist.notFound"),
                    systemImage: "list.bullet.rectangle",
                    description: Text(String(localized: "playlist.notFound.description"))
                )
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
        }
        #if !os(tvOS)
        .navigationTitle(navigationTitleText)
        .toolbarTitleDisplayMode(.inlineLarge)
        .navigationSubtitleIfAvailable(playlistSummaryText)
        #endif
        #if os(macOS)
        .toolbar {
            ToolbarItem(placement: .principal) {
                macOSNavigationTitle
            }
        }
        #endif
        .sheet(isPresented: $showingEditSheet) {
            if let localPlaylist {
                PlaylistFormSheet(mode: .edit(localPlaylist)) { newTitle, newDescription in
                    dataManager?.updatePlaylist(localPlaylist, title: newTitle, description: newDescription)
                    loadLocalPlaylist()
                }
            }
        }
        .confirmationDialog(
            String(localized: "playlist.delete.confirm"),
            isPresented: $showingDeleteConfirmation,
            titleVisibility: .visible
        ) {
            Button(String(localized: "common.delete"), role: .destructive) {
                if let localPlaylist {
                    dataManager?.deletePlaylist(localPlaylist)
                    dismiss()
                }
            }
        }
        .presentationCompactAdaptation(.sheet)
        .task {
            await loadPlaylist()
        }
        .onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in
            loadWatchEntries()
        }
        #if !os(tvOS)
        .batchDownload(coordinator: downloadCoordinator)
        .onAppear {
            downloadCoordinator.setEnvironment(appEnvironment)
            loadDownloadStateIfNeeded()
        }
        .onChange(of: videos) { _, _ in
            // Update cache when videos change (e.g., after loading)
            updateAllVideosDownloadedCache()
        }
        #endif
    }

    // MARK: - Content

    #if os(macOS)
    private var macOSNavigationTitle: some View {
        HStack(spacing: 8) {
            LazyImage(url: navigationThumbnailURL) { state in
                if let image = state.image {
                    image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                } else {
                    RoundedRectangle(cornerRadius: 3)
                        .fill(.quaternary)
                        .overlay {
                            Image(systemName: "list.bullet.rectangle")
                                .font(.caption2)
                                .foregroundStyle(.secondary)
                        }
                }
            }
            .frame(width: 26, height: 15)
            .clipShape(RoundedRectangle(cornerRadius: 3))

            Text(navigationTitleText)
                .font(.headline)
                .lineLimit(1)
        }
        .padding(.horizontal, 10)
    }
    #endif

    @ViewBuilder
    private var playlistContent: some View {
        #if os(tvOS)
        tvOSTwoColumnContent
        #else
        scrollablePlaylistContent()
        #endif
    }

    @ViewBuilder
    private func scrollablePlaylistContent() -> some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 0) {
                playlistHeader()

                Divider()
                    .padding(.horizontal)

                playlistBody
            }
        }
        .refreshable {
            if case .remote = source {
                await loadPlaylist()
            }
        }
    }

    @ViewBuilder
    private var playlistBody: some View {
        if videos.isEmpty {
            if isLoading {
                ProgressView()
                    .frame(maxWidth: .infinity)
                    .padding(.top, 40)
            } else {
                emptyPlaylistView
            }
        } else {
            videoList
        }
    }

    /// Shows cached header with a spinner below while loading full playlist data.
    private func loadingContent(_ cached: CachedPlaylistHeader) -> some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 0) {
                playlistHeader(
                    thumbnailURL: cached.thumbnailURL,
                    videoCount: cached.videoCount
                )

                Divider()
                    .padding(.horizontal)

                // Centered spinner for content area
                ProgressView()
                    .frame(maxWidth: .infinity)
                    .padding(.top, 40)
            }
        }
    }

    private func playlistHeader() -> some View {
        playlistHeader(
            thumbnailURL: thumbnailURL,
            videoCount: videoCount,
            localPlaylist: localPlaylist,
            descriptionText: descriptionText
        )
    }

    private func playlistHeader(
        thumbnailURL: URL?,
        videoCount: Int,
        localPlaylist: LocalPlaylist? = nil,
        descriptionText: String? = nil
    ) -> some View {
        // Build summary text for this header instance
        let summaryText: String? = {
            var parts: [String] = []
            if videoCount > 0 || !isLoading {
                parts.append(String(localized: "playlist.videoCount \(videoCount)"))
            }
            if let localPlaylist {
                parts.append(localPlaylist.formattedTotalDuration)
            }
            return parts.isEmpty ? nil : parts.joined(separator: " · ")
        }()

        return VStack(alignment: .leading, spacing: 12) {
            // Summary info (only shown on pre-iOS 26, or non-iOS platforms)
            #if os(iOS)
            if #unavailable(iOS 26) {
                if let summaryText {
                    Text(summaryText)
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
            }
            #elseif os(macOS)
            if let summaryText {
                Text(summaryText)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }
            #endif

            // Description (full width, expandable)
            if let descriptionText, !descriptionText.isEmpty {
                ExpandableText(text: descriptionText, lineLimit: 2, isExpanded: $isDescriptionExpanded)
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }

            // Action buttons row (iOS/macOS only)
            #if !os(tvOS)
            if !videos.isEmpty || localPlaylist != nil {
                playlistActionButtons
            }
            #endif
        }
        .padding()
    }

    // MARK: - Action Buttons Row

    #if !os(tvOS)
    @ViewBuilder
    private var playlistActionButtons: some View {
        HStack(spacing: 12) {
            // Play button (only when queue is enabled)
            if isQueueEnabled {
                Button {
                    playAll()
                } label: {
                    Label(String(localized: "playlist.play"), systemImage: "play.fill")
                        .fontWeight(.semibold)
                }
                .buttonStyle(.borderedProminent)
            }

            // Download button with three states
            if downloadCoordinator.isDownloading {
                Button {
                    // No action while downloading
                } label: {
                    HStack(spacing: 6) {
                        ProgressView()
                            .controlSize(.small)
                        if let progress = downloadCoordinator.progress {
                            Text("\(progress.current)/\(progress.total)")
                                .monospacedDigit()
                        }
                    }
                }
                .fontWeight(.semibold)
                .buttonStyle(.bordered)
                .disabled(true)
            } else if cachedAllVideosDownloaded {
                Button {
                    // No action - already downloaded
                } label: {
                    Label(String(localized: "playlist.downloaded"), systemImage: "checkmark.circle.fill")
                }
                .fontWeight(.semibold)
                .buttonStyle(.bordered)
                .disabled(true)
            } else {
                Button {
                    downloadCoordinator.startDownload(videos: videos)
                } label: {
                    Label(String(localized: "playlist.downloadAll"), systemImage: "arrow.down.circle")
                }
                .fontWeight(.semibold)
                .buttonStyle(.bordered)
            }

            Spacer()

            // Menu button (circular with glass background)
            Menu {
                // Remote-only: Save to Library, Share
                if case .remote = source, let remotePlaylist, !remotePlaylist.isLocal {
                    Button {
                        Task { await importToLocal() }
                    } label: {
                        if isImporting, let progress = importProgress {
                            Label(String(localized: "playlist.savingToLibrary \(progress.current) \(progress.total)"), systemImage: "plus.rectangle.on.folder")
                        } else {
                            Label(String(localized: "playlist.saveToLibrary"), systemImage: "plus.rectangle.on.folder")
                        }
                    }
                    .disabled(isImporting)

                    ShareLink(item: playlistShareURL()) {
                        Label(String(localized: "common.share"), systemImage: "square.and.arrow.up")
                    }
                }

                // Local-only: Edit, Delete
                if isLocal, localPlaylist != nil {
                    Button {
                        showingEditSheet = true
                    } label: {
                        Label(String(localized: "playlist.edit"), systemImage: "pencil")
                    }

                    Button(role: .destructive) {
                        showingDeleteConfirmation = true
                    } label: {
                        Label(String(localized: "playlist.delete"), systemImage: "trash")
                    }
                }
            } label: {
                Image(systemName: "ellipsis")
                    .font(.body.weight(.medium))
                    .frame(width: 44, height: 44)
                    .background(.regularMaterial, in: Circle())
            }
            .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
        }
        .padding(.top, 8)
    }
    #endif

    // MARK: - tvOS Two-Column Layout

    #if os(tvOS)
    @ViewBuilder
    private var tvOSTwoColumnContent: some View {
        GeometryReader { geometry in
            let leftWidth = geometry.size.width * 0.30
            HStack(alignment: .top, spacing: 40) {
                tvOSLeftColumn
                    .frame(width: leftWidth, alignment: .leading)
                    .focusSection()

                tvOSRightColumn
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                    .focusSection()
            }
            .padding(.horizontal, 60)
            .padding(.vertical, 40)
        }
    }

    @ViewBuilder
    private var tvOSLeftColumn: some View {
        VStack(alignment: .leading, spacing: 24) {
            tvOSPlaylistThumbnail
                .frame(maxWidth: .infinity)

            if !title.isEmpty {
                Text(title)
                    .font(.title2)
                    .fontWeight(.bold)
                    .foregroundStyle(.white)
                    .fixedSize(horizontal: false, vertical: true)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }

            if let summaryText = playlistSummaryText {
                Text(summaryText)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            if !isDescriptionScrollLocked, !videos.isEmpty || localPlaylist != nil {
                tvOSPlaylistActionButtons
            }

            if let descriptionText, !descriptionText.isEmpty {
                TVScrollableDescription(
                    description: descriptionText,
                    isScrollLocked: $isDescriptionScrollLocked,
                    showsHeader: false
                )
                .frame(maxHeight: .infinity)
            } else {
                Spacer(minLength: 0)
            }
        }
        .padding(.vertical, 8)
        .frame(maxHeight: .infinity, alignment: .top)
        .animation(.easeInOut(duration: 0.25), value: isDescriptionScrollLocked)
    }

    @ViewBuilder
    private var tvOSPlaylistThumbnail: some View {
        let urls = (videos.first?.thumbnailURLsByQuality ?? [])
            + [Thumbnail.reliableURL(for: thumbnailURL)].compactMap { $0 }
        FallbackLazyImage(urls: urls) { state in
            if let image = state.image {
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } else {
                Rectangle()
                    .fill(.ultraThinMaterial)
                    .overlay {
                        Image(systemName: "music.note.list")
                            .font(.system(size: 56, weight: .semibold))
                            .foregroundStyle(.secondary)
                    }
            }
        }
        .aspectRatio(16.0 / 9.0, contentMode: .fit)
        .clipShape(RoundedRectangle(cornerRadius: 12))
        .shadow(color: .black.opacity(0.4), radius: 10, y: 5)
    }

    @ViewBuilder
    private var tvOSRightColumn: some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 0) {
                playlistBody
            }
            .padding(.vertical, 20)
        }
        .scrollClipDisabled()
    }

    @ViewBuilder
    private var tvOSPlaylistActionButtons: some View {
        VStack(spacing: 12) {
            // Play (only when queue is enabled)
            if isQueueEnabled {
                Button {
                    playAll()
                } label: {
                    Label(String(localized: "playlist.play"), systemImage: "play.fill")
                        .frame(maxWidth: .infinity, minHeight: 50)
                        .font(.headline)
                }
                .buttonStyle(.borderedProminent)
            }

            // Remote-only: Save to Library
            if case .remote = source, let remotePlaylist, !remotePlaylist.isLocal {
                Button {
                    Task { await importToLocal() }
                } label: {
                    Group {
                        if isImporting, let progress = importProgress {
                            Label(String(localized: "playlist.savingToLibrary \(progress.current) \(progress.total)"), systemImage: "plus.rectangle.on.folder")
                        } else {
                            Label(String(localized: "playlist.saveToLibrary"), systemImage: "plus.rectangle.on.folder")
                        }
                    }
                    .frame(maxWidth: .infinity, minHeight: 50)
                    .font(.headline)
                }
                .buttonStyle(.bordered)
                .disabled(isImporting)
            }

            // Local-only: Edit, Delete
            if isLocal, localPlaylist != nil {
                Button {
                    showingEditSheet = true
                } label: {
                    Label(String(localized: "playlist.edit"), systemImage: "pencil")
                        .frame(maxWidth: .infinity, minHeight: 50)
                        .font(.headline)
                }
                .buttonStyle(.bordered)

                Button {
                    showingDeleteConfirmation = true
                } label: {
                    Label(String(localized: "playlist.delete"), systemImage: "trash")
                        .frame(maxWidth: .infinity, minHeight: 50)
                        .font(.headline)
                        .foregroundStyle(.red)
                }
                .buttonStyle(.bordered)
            }
        }
    }
    #endif

    #if !os(tvOS)
    /// Loads download state once on appear to avoid continuous re-renders from @Observable.
    private func loadDownloadStateIfNeeded() {
        guard !hasLoadedDownloadState else { return }
        hasLoadedDownloadState = true
        updateAllVideosDownloadedCache()
    }

    /// Updates the cached allVideosDownloaded state.
    /// Called on appear and when videos array changes.
    private func updateAllVideosDownloadedCache() {
        guard let downloadManager = appEnvironment?.downloadManager else {
            cachedAllVideosDownloaded = false
            return
        }
        cachedAllVideosDownloaded = videos.allSatisfy { video in
            downloadManager.downloadedVideoIDs.contains(video.id) ||
            downloadManager.downloadingVideoIDs.contains(video.id)
        }
    }
    #endif

    private var emptyPlaylistView: some View {
        ContentUnavailableView {
            Label(String(localized: "playlist.empty"), systemImage: "music.note.list")
        } description: {
            Text(String(localized: "playlist.empty.description"))
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .padding(.top, 40)
    }

    private var videoList: some View {
        #if os(tvOS)
        let indexColumnWidth: CGFloat = 60
        #else
        let indexColumnWidth: CGFloat = 32
        #endif
        return LazyVStack(spacing: 0) {
            ForEach(Array(videos.enumerated()), id: \.element.id) { index, video in
                VideoListRow(
                    isLast: index == videos.count - 1,
                    rowStyle: .regular,
                    listStyle: .plain,
                    indexWidth: indexColumnWidth  // Index column width in VideoRowView
                ) {
                    Button {
                        playFromIndex(index)
                    } label: {
                        PlaylistVideoRowView(
                            index: index + 1,
                            video: video,
                            watchProgress: watchProgress(for: video),
                            onRemove: isLocal ? { removeVideo(at: index) } : nil
                        )
                    }
                    .buttonStyle(.plain)
                }
            }
        }
    }

    // MARK: - Error View

    private func errorView(_ error: String) -> some View {
        ContentUnavailableView {
            Label(String(localized: "common.error"), systemImage: "exclamationmark.triangle")
        } description: {
            Text(error)
        } actions: {
            Button(String(localized: "common.retry")) {
                Task { await loadPlaylist() }
            }
            .buttonStyle(.bordered)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }

    // MARK: - Data Loading

    private func loadPlaylist() async {
        switch source {
        case .local:
            loadLocalPlaylist()
        case .remote(let playlistID, let instance, _):
            await loadRemotePlaylist(playlistID: playlistID, instance: instance)
        }
        loadWatchEntries()
    }

    private func loadWatchEntries() {
        watchEntriesMap = dataManager?.watchEntriesMap() ?? [:]
    }

    private func watchProgress(for video: Video) -> Double? {
        guard let entry = watchEntriesMap[video.id.videoID] else { return nil }
        let progress = entry.progress
        return progress > 0 && progress < 1 ? progress : nil
    }

    private func loadLocalPlaylist() {
        guard case .local(let uuid, _) = source else { return }

        localPlaylist = dataManager?.playlists().first { $0.id == uuid }

        if let playlist = localPlaylist {
            title = playlist.title
            descriptionText = playlist.playlistDescription
            thumbnailURL = playlist.thumbnailURL
            videos = playlist.sortedItems.map { $0.toVideo() }
            videoCount = playlist.videoCount
        }

        isLoading = false
    }

    private func loadRemotePlaylist(playlistID: PlaylistID, instance: Instance?) async {
        guard let appEnvironment else {
            errorMessage = "App not initialized"
            isLoading = false
            return
        }

        // Use passed instance, or find appropriate one based on source
        let targetInstance: Instance?
        if let instance {
            targetInstance = instance
        } else if let playlistSource = playlistID.source {
            targetInstance = instanceForSource(playlistSource, instancesManager: appEnvironment.instancesManager)
        } else {
            targetInstance = appEnvironment.instancesManager.activeInstance
        }

        guard let resolvedInstance = targetInstance else {
            errorMessage = "No instance available"
            isLoading = false
            return
        }

        isLoading = true
        errorMessage = nil

        // Load cached header data for immediate display
        if let recentPlaylist = appEnvironment.dataManager.recentPlaylistEntry(forPlaylistID: playlistID.playlistID) {
            cachedHeader = CachedPlaylistHeader(from: recentPlaylist)
        }

        do {
            let fetchedPlaylist: Playlist

            // Check if this is an Invidious user playlist (IVPL prefix) that requires authentication
            let isInvidiousUserPlaylist = playlistID.playlistID.hasPrefix("IVPL")
            let isInvidiousInstance = resolvedInstance.type == .invidious
            let sid = appEnvironment.invidiousCredentialsManager.sid(for: resolvedInstance)

            if isInvidiousUserPlaylist && isInvidiousInstance, let sid {
                // Use authenticated endpoint for Invidious user playlists (including private ones)
                let api = InvidiousAPI(httpClient: appEnvironment.httpClient)
                fetchedPlaylist = try await api.userPlaylist(
                    id: playlistID.playlistID,
                    instance: resolvedInstance,
                    sid: sid
                )
            } else {
                // Use public endpoint for regular playlists
                fetchedPlaylist = try await appEnvironment.contentService.playlist(
                    id: playlistID.playlistID,
                    instance: resolvedInstance
                )
            }

            remotePlaylist = fetchedPlaylist
            title = fetchedPlaylist.title
            descriptionText = fetchedPlaylist.description
            thumbnailURL = fetchedPlaylist.thumbnailURL
            videos = fetchedPlaylist.videos
            videoCount = fetchedPlaylist.videoCount

            // Save to recent playlists (only remote playlists, unless incognito mode is enabled or recent playlists disabled)
            if appEnvironment.settingsManager.incognitoModeEnabled != true,
               appEnvironment.settingsManager.saveRecentPlaylists {
                appEnvironment.dataManager.addRecentPlaylist(fetchedPlaylist)
            }
        } catch {
            errorMessage = error.localizedDescription
        }

        isLoading = false
    }

    /// Returns the appropriate instance for the playlist's content source.
    private func instanceForSource(_ source: ContentSource, instancesManager: InstancesManager) -> Instance? {
        switch source {
        case .global:
            // For global content (YouTube), prefer Invidious instances over Yattee Server
            // since Invidious playlists (IVPL*) are Invidious-specific
            if let invidiousInstance = instancesManager.enabledInstances.first(where: { $0.type == .invidious }) {
                return invidiousInstance
            }
            // Fall back to any YouTube-compatible instance
            return instancesManager.enabledInstances.first(where: { $0.isYouTubeInstance })
        case .federated(let provider, let instanceURL):
            // For federated content, find matching instance by URL
            if let existingInstance = instancesManager.instances.first(where: { $0.url == instanceURL }) {
                return existingInstance
            }
            // If no configured instance matches, create a temporary one for PeerTube
            if provider == ContentSource.peertubeProvider {
                return Instance(type: .peertube, url: instanceURL)
            }
            return nil
        case .extracted:
            // For extracted content, use Yattee Server
            return instancesManager.yatteeServerInstances.first
        }
    }

    // MARK: - Playback Actions

    private func playFromIndex(_ index: Int) {
        guard let appEnvironment else { return }

        appEnvironment.queueManager.playFromList(
            videos: videos,
            index: index,
            queueSource: queueSource,
            sourceLabel: title
        )
    }

    private func playAll() {
        guard !videos.isEmpty, let appEnvironment else { return }

        appEnvironment.queueManager.playFromList(
            videos: videos,
            index: 0,
            queueSource: queueSource,
            sourceLabel: title
        )
    }

    private var queueSource: QueueSource {
        switch source {
        case .local:
            return .manual
        case .remote(let playlistID, _, _):
            return .playlist(playlistID: playlistID.playlistID, continuation: nil)
        }
    }

    // MARK: - Local Playlist Actions

    private func removeVideo(at index: Int) {
        guard let localPlaylist else { return }
        dataManager?.removeVideoFromPlaylist(at: index, playlist: localPlaylist)
        loadLocalPlaylist()
    }

    // MARK: - Remote Playlist Actions

    private func playlistShareURL() -> URL {
        guard let remotePlaylist else {
            return URL(string: "yattee://playlist")!
        }

        switch remotePlaylist.id.source {
        case .global:
            return URL(string: "https://youtube.com/playlist?list=\(remotePlaylist.id.playlistID)")!
        case .federated(_, let instance):
            return instance.appendingPathComponent("video-playlists/\(remotePlaylist.id.playlistID)")
        case .extracted(_, let originalURL):
            return originalURL
        case nil:
            return URL(string: "yattee://playlist/\(remotePlaylist.id.playlistID)")!
        }
    }

    /// Generates a unique playlist name by appending (2), (3), etc. if needed.
    private func generateUniqueName(_ baseName: String, existingTitles: Set<String>) -> String {
        if !existingTitles.contains(baseName) {
            return baseName
        }

        var counter = 2
        while true {
            let candidateName = "\(baseName) (\(counter))"
            if !existingTitles.contains(candidateName) {
                return candidateName
            }
            counter += 1
        }
    }

    /// Imports the remote playlist to local storage.
    private func importToLocal() async {
        guard let appEnvironment, let remotePlaylist else { return }

        isImporting = true
        importProgress = nil

        // Generate unique name
        let existingTitles = Set(appEnvironment.dataManager.playlists().map(\.title))
        let uniqueName = generateUniqueName(remotePlaylist.title, existingTitles: existingTitles)

        // Create local playlist
        let newLocalPlaylist = appEnvironment.dataManager.createPlaylist(
            title: uniqueName,
            description: remotePlaylist.description
        )

        // Add videos
        let total = videos.count

        for (index, video) in videos.enumerated() {
            importProgress = (current: index + 1, total: total)
            appEnvironment.dataManager.addToPlaylist(video, playlist: newLocalPlaylist)

            // Small delay to allow UI to update for larger playlists
            if total > 10 {
                try? await Task.sleep(for: .milliseconds(30))
            }
        }

        isImporting = false
        importProgress = nil

        appEnvironment.toastManager.showSuccess(String(localized: "playlist.imported.title"))
    }
}

// MARK: - Preview

#Preview("Local Playlist") {
    NavigationStack {
        UnifiedPlaylistDetailView(source: .local(UUID()))
    }
    .appEnvironment(.preview)
}

#Preview("Remote Playlist") {
    NavigationStack {
        UnifiedPlaylistDetailView(source: .remote(.global("PLtest"), instance: nil))
    }
    .appEnvironment(.preview)
}
