//
//  MediaBrowserView.swift
//  Yattee
//
//  View for browsing files in a media source.
//

import SwiftUI

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

    let source: MediaSource
    let initialPath: String

    @Namespace private var sheetTransition
    @State private var currentPath: String
    @State private var files: [MediaFile] = []
    @State private var isLoading = false
    @State private var error: MediaSourceError?
    @State private var showOnlyPlayable: Bool
    @State private var sortOrder: MediaBrowserSortOrder
    @State private var sortAscending: Bool
    @State private var showViewOptions = false
    #if os(tvOS)
    @State private var unsupportedFile: MediaFile?
    @FocusState private var firstFileFocused: Bool
    #endif

    private var listStyle: VideoListStyle {
        appEnvironment?.settingsManager.listStyle ?? .inset
    }

    init(source: MediaSource, path: String = "/", showOnlyPlayable: Bool = false) {
        self.source = source
        self.initialPath = path
        _currentPath = State(initialValue: path)

        let defaults = UserDefaults.standard
        let key = "mediaBrowser.\(source.id.uuidString)"

        if let raw = defaults.string(forKey: "\(key).sortOrder"),
           let saved = MediaBrowserSortOrder(rawValue: raw) {
            _sortOrder = State(initialValue: saved)
        } else {
            _sortOrder = State(initialValue: .name)
        }

        _sortAscending = State(initialValue: defaults.object(forKey: "\(key).sortAscending") as? Bool ?? true)
        _showOnlyPlayable = State(initialValue: defaults.object(forKey: "\(key).showOnlyPlayable") as? Bool ?? showOnlyPlayable)
    }

    /// Files filtered and sorted based on current settings.
    private var displayedFiles: [MediaFile] {
        var result = files
        if showOnlyPlayable {
            result = result.filter { $0.isDirectory || $0.isPlayable }
        }
        return sortedFiles(result)
    }

    private var viewOptionsSheetContent: some View {
        MediaBrowserViewOptionsSheet(
            sourceType: source.type,
            sortOrder: $sortOrder,
            sortAscending: $sortAscending,
            showOnlyPlayable: $showOnlyPlayable
        )
    }

    /// View options button lives on the leading edge on macOS, trailing elsewhere.
    private var viewOptionsPlacement: ToolbarItemPlacement {
        #if os(macOS)
        .navigation
        #else
        .primaryAction
        #endif
    }

    var body: some View {
        content
            #if !os(tvOS)
            .navigationTitle(navigationTitle)
            .toolbarTitleDisplayMode(.inlineLarge)
            #endif
            .toolbar {
                #if !os(tvOS)
                ToolbarItem(placement: .primaryAction) {
                    if isLoading {
                        ProgressView()
                            .controlSize(.small)
                    } else {
                        Button {
                            Task { await loadFiles() }
                        } label: {
                            Label(String(localized: "common.refresh"), systemImage: "arrow.clockwise")
                        }
                    }
                }
                ToolbarItem(placement: viewOptionsPlacement) {
                    Button {
                        showViewOptions = true
                    } label: {
                        Label(
                            String(localized: "viewOptions.title"),
                            systemImage: showOnlyPlayable
                                ? "line.3.horizontal.decrease.circle.fill"
                                : "line.3.horizontal.decrease.circle"
                        )
                    }
                    .liquidGlassTransitionSource(id: "mediaBrowserViewOptions", in: sheetTransition)
                    #if os(macOS)
                    .popover(isPresented: $showViewOptions, arrowEdge: .bottom) {
                        viewOptionsSheetContent
                    }
                    #endif
                }
                #endif
            }
            #if !os(macOS)
            .sheet(isPresented: $showViewOptions) {
                viewOptionsSheetContent
                    .liquidGlassSheetContent(sourceID: "mediaBrowserViewOptions", in: sheetTransition)
            }
            #endif
            .task {
                await loadFiles()
            }
            .onChange(of: sortOrder) { _, newValue in
                savePreference(newValue.rawValue, forKey: "sortOrder")
            }
            .onChange(of: sortAscending) { _, newValue in
                savePreference(newValue, forKey: "sortAscending")
            }
            .onChange(of: showOnlyPlayable) { _, newValue in
                savePreference(newValue, forKey: "showOnlyPlayable")
            }
            #if os(tvOS)
            .onChange(of: displayedFiles.isEmpty) { wasEmpty, isEmpty in
                if wasEmpty && !isEmpty {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                        firstFileFocused = true
                    }
                }
            }
            #endif
    }

    @ViewBuilder
    private var content: some View {
        #if os(tvOS)
        TVSidebarDetailContainer(
            systemImage: "folder",
            title: navigationTitle
        ) {
            VStack(spacing: 0) {
                if !(isLoading && files.isEmpty) {
                    HStack(spacing: 24) {
                        Button {
                            Task { await loadFiles() }
                        } label: {
                            Label(String(localized: "common.refresh"), systemImage: "arrow.clockwise")
                        }
                        Button {
                            showViewOptions = true
                        } label: {
                            Label(
                                String(localized: "viewOptions.title"),
                                systemImage: showOnlyPlayable
                                    ? "line.3.horizontal.decrease.circle.fill"
                                    : "line.3.horizontal.decrease.circle"
                            )
                        }
                        Spacer()
                    }
                    .padding(.horizontal, 24)
                    .padding(.top, 16)
                    .padding(.bottom, 8)
                    .focusSection()
                }
                inner
                    .focusSection()
            }
        }
        #else
        inner
        #endif
    }

    @ViewBuilder
    private var inner: some View {
        Group {
            if isLoading && files.isEmpty {
                ProgressView()
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            } else if let error {
                ContentUnavailableView {
                    Label(String(localized: "common.error"), systemImage: "exclamationmark.triangle")
                } description: {
                    Text(error.localizedDescription)
                } actions: {
                    Button(String(localized: "common.retry")) {
                        Task { await loadFiles() }
                    }
                    .buttonStyle(.borderedProminent)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            } else if files.isEmpty {
                ContentUnavailableView {
                    Label(String(localized: "mediaBrowser.emptyFolder"), systemImage: "folder")
                } description: {
                    Text(String(localized: "mediaBrowser.emptyFolder.description"))
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            } else {
                fileList
            }
        }
    }

    private var navigationTitle: String {
        if currentPath == "/" || currentPath.isEmpty {
            return source.name
        }
        return (currentPath as NSString).lastPathComponent
    }

    private var fileListBackground: Color {
        listStyle == .inset ? ListBackgroundStyle.grouped.color : ListBackgroundStyle.plain.color
    }

    private var fileListScrollView: some View {
        ScrollView {
            LazyVStack(spacing: 0) {
                sectionCard {
                    ForEach(Array(displayedFiles.enumerated()), id: \.element.id) { index, file in
                        let isLast = index == displayedFiles.count - 1
                        let isFirst = index == 0

                        SourceListRow(isLast: isLast, listStyle: listStyle) {
                            if file.isDirectory {
                                NavigationLink(value: NavigationDestination.mediaBrowser(source, path: file.path, showOnlyPlayable: showOnlyPlayable)) {
                                    MediaFileRow(file: file, sortOrder: sortOrder)
                                }
                                #if os(macOS)
                                .buttonStyle(.plain)
                                #else
                                .foregroundStyle(.primary)
                                #endif
                            } else if file.isPlayable {
                                playableFileRow(for: file)
                            } else {
                                #if os(tvOS)
                                MediaFileTVOSUnsupportedButton(onTap: { unsupportedFile = file }) {
                                    MediaFileRow(file: file, sortOrder: sortOrder)
                                }
                                #else
                                MediaFileRow(file: file, sortOrder: sortOrder)
                                #endif
                            }
                        }
                        #if os(tvOS)
                        .modifier(FirstRowFocusModifier(isFirst: isFirst, focus: $firstFileFocused))
                        #endif
                    }
                }
            }
            .padding(.top, 16)
        }
    }

    @ViewBuilder
    private var fileList: some View {
        #if os(tvOS)
        // On tvOS do NOT ignoreSafeArea — otherwise the ScrollView extends
        // under TVSidebarDetailContainer's 400pt leading inset, and left-press
        // from a file row escapes to the outer sidebarAdaptable TabView
        // instead of reaching the inline Refresh / View Options buttons.
        fileListBackground
            .overlay(fileListScrollView)
            .alert(
                String(localized: "mediaBrowser.unsupportedFile.title"),
                isPresented: Binding(
                    get: { unsupportedFile != nil },
                    set: { if !$0 { unsupportedFile = nil } }
                ),
                presenting: unsupportedFile
            ) { _ in
                Button(String(localized: "common.ok"), role: .cancel) { unsupportedFile = nil }
            } message: { file in
                Text(String(localized: "mediaBrowser.unsupportedFile.message \(file.name)"))
            }
        #else
        fileListBackground
            .ignoresSafeArea()
            .overlay(fileListScrollView)
        #endif
    }

    @ViewBuilder
    private func sectionCard<Content: View>(@ViewBuilder content: () -> Content) -> some View {
        #if os(macOS)
        VStack(spacing: 0) {
            Divider()
            LazyVStack(spacing: 0) {
                content()
            }
            Divider()
        }
        .padding(.bottom, 12)
        #else
        if listStyle == .inset {
            LazyVStack(spacing: 0) {
                content()
            }
            .background(ListBackgroundStyle.card.color)
            .clipShape(RoundedRectangle(cornerRadius: 10))
            .padding(.horizontal, 16)
            .padding(.bottom, 16)
        } else {
            LazyVStack(spacing: 0) {
                content()
            }
            .padding(.bottom, 16)
        }
        #endif
    }

    // MARK: - Preferences

    private func savePreference(_ value: Any, forKey suffix: String) {
        UserDefaults.standard.set(value, forKey: "mediaBrowser.\(source.id.uuidString).\(suffix)")
    }

    // MARK: - Loading

    @MainActor
    private func loadFiles() async {
        guard let appEnvironment else { return }

        isLoading = true
        error = nil

        do {
            let loadedFiles: [MediaFile]

            switch source.type {
            case .webdav:
                let password = appEnvironment.mediaSourcesManager.password(for: source)
                loadedFiles = try await appEnvironment.webDAVClient.listFiles(
                    at: currentPath,
                    source: source,
                    password: password
                )

            case .smb:
                let password = appEnvironment.mediaSourcesManager.password(for: source)
                loadedFiles = try await appEnvironment.smbClient.listFiles(
                    at: currentPath,
                    source: source,
                    password: password
                )

            case .localFolder:
                loadedFiles = try await appEnvironment.localFileClient.listFiles(
                    at: currentPath,
                    source: source
                )
            }

            files = loadedFiles
            isLoading = false
        } catch let err as MediaSourceError {
            error = err
            isLoading = false
        } catch {
            self.error = .unknown(error.localizedDescription)
            isLoading = false
        }
    }

    private func sortedFiles(_ files: [MediaFile]) -> [MediaFile] {
        files.sorted { lhs, rhs in
            // Directories always first
            if lhs.isDirectory != rhs.isDirectory {
                return lhs.isDirectory
            }

            let comparison: ComparisonResult
            switch sortOrder {
            case .name:
                comparison = lhs.name.localizedCaseInsensitiveCompare(rhs.name)
            case .dateModified:
                let lhsDate = lhs.modifiedDate ?? .distantPast
                let rhsDate = rhs.modifiedDate ?? .distantPast
                comparison = lhsDate.compare(rhsDate)
            case .dateCreated:
                let lhsDate = lhs.createdDate ?? .distantPast
                let rhsDate = rhs.createdDate ?? .distantPast
                comparison = lhsDate.compare(rhsDate)
            }

            return sortAscending ? comparison == .orderedAscending : comparison == .orderedDescending
        }
    }

    // MARK: - Playable row composition

    @ViewBuilder
    private func playableFileRow(for file: MediaFile) -> some View {
        #if os(tvOS)
        MediaFileTVOSTapButton(
            onPlay: { playFile(file) },
            onOpenInfo: { openInfo(for: file) }
        ) {
            MediaFileRow(file: file, sortOrder: sortOrder)
        }
        .videoContextMenu(video: file.toVideo(), context: .mediaBrowser)
        #else
        MediaFileRow(
            file: file,
            sortOrder: sortOrder,
            iconAreaModifier: { view in
                AnyView(
                    view.mediaFileRegionTap(
                        action: appEnvironment?.settingsManager.thumbnailTapAction ?? .playVideo,
                        onPlay: { playFile(file) },
                        onOpenInfo: { openInfo(for: file) }
                    )
                )
            },
            textAreaModifier: { view in
                AnyView(
                    view.mediaFileRegionTap(
                        action: appEnvironment?.settingsManager.textAreaTapAction ?? .openInfo,
                        onPlay: { playFile(file) },
                        onOpenInfo: { openInfo(for: file) }
                    )
                )
            }
        )
        .contentShape(Rectangle())
        .onTapGesture { playFile(file) }
        .videoContextMenu(video: file.toVideo(), context: .mediaBrowser)
        #endif
    }

    // MARK: - Playback

    private func openInfo(for file: MediaFile) {
        guard let appEnvironment else { return }

        let playableFiles = displayedFiles.filter { $0.isPlayable }
        let videos = playableFiles.map { $0.toVideo() }
        let index = playableFiles.firstIndex(where: { $0.id == file.id }) ?? 0
        let folderPath = (file.path as NSString).deletingLastPathComponent
        let folderName = (folderPath as NSString).lastPathComponent

        let context = VideoQueueContext(
            video: file.toVideo(),
            queueSource: .mediaBrowser(sourceID: source.id, folderPath: folderPath),
            sourceLabel: folderName.isEmpty ? source.name : folderName,
            videoList: videos,
            videoIndex: index,
            startTime: nil,
            loadMoreVideos: nil,
            mediaBrowserPlayback: MediaBrowserPlaybackInfo(
                source: source,
                allFilesInFolder: files
            )
        )

        appEnvironment.navigationCoordinator.navigate(
            to: .video(.loaded(file.toVideo()), queueContext: context)
        )
    }

    private func playFile(_ file: MediaFile) {
        guard let appEnvironment else { return }

        // Get all playable files in current sort order
        let playableFiles = displayedFiles.filter { $0.isPlayable }

        // Find the index of the tapped file in the playable files list
        guard let playableIndex = playableFiles.firstIndex(where: { $0.id == file.id }) else {
            return
        }

        // Use queue-based playback with all files in the folder
        // Stream and captions are resolved on-demand when each video plays
        appEnvironment.queueManager.playFromMediaBrowser(
            files: playableFiles,
            index: playableIndex,
            source: source,
            allFilesInFolder: files  // All files including subtitles for discovery
        )
    }
}

#if os(tvOS)
private struct FirstRowFocusModifier: ViewModifier {
    let isFirst: Bool
    var focus: FocusState<Bool>.Binding

    func body(content: Content) -> some View {
        if isFirst {
            content.focused(focus)
        } else {
            content
        }
    }
}
#endif

// MARK: - Preview

#Preview {
    NavigationStack {
        MediaBrowserView(
            source: .webdav(name: "My NAS", url: URL(string: "https://nas.local:5006")!)
        )
    }
    .appEnvironment(.preview)
}
