//
//  QualitySelectorView+StreamHelpers.swift
//  Yattee
//
//  Stream filtering, sorting, and helper methods for QualitySelectorView.
//

import SwiftUI

extension QualitySelectorView {
    // MARK: - Stream Categories

    /// Adaptive streams (HLS, DASH) - auto quality selection.
    /// Deduplicated by URL to avoid showing identical entries.
    var adaptiveStreams: [Stream] {
        let adaptive = streams.filter { (stream: Stream) -> Bool in
            let format = StreamFormat.detect(from: stream)
            return format == .hls || format == .dash
        }

        // Deduplicate by URL - keep first occurrence
        var seenURLs: Set<URL> = []
        return adaptive.filter { stream in
            if seenURLs.contains(stream.url) {
                return false
            }
            seenURLs.insert(stream.url)
            return true
        }
    }

    /// Video streams (both muxed and video-only), sorted with preferred quality first.
    /// When showAdvancedStreamDetails is false, filters to best stream per resolution.
    /// Downloaded streams (local files) are always included and shown first.
    var videoStreams: [Stream] {
        let maxRes: StreamResolution? = preferredQuality.maxResolution

        // Separate downloaded/media-source streams (always include them, shown
        // first). Local files are file URLs; WebDAV/SMB media-source streams
        // are remote URLs that carry no resolution metadata — nothing else
        // produces a non-adaptive remote stream without resolution, so that
        // combination identifies them.
        let downloadedStreams: [Stream] = streams.filter { (stream: Stream) -> Bool in
            guard !stream.isAudioOnly else { return false }
            if stream.url.isFileURL { return true }
            let format = StreamFormat.detect(from: stream)
            return stream.resolution == nil && format != .hls && format != .dash
        }

        // Online streams need resolution to be shown
        let onlineVideoStreams: [Stream] = streams
            .filter { (stream: Stream) -> Bool in
                !stream.url.isFileURL && !stream.isAudioOnly && stream.resolution != nil
            }
            .filter { (stream: Stream) -> Bool in
                let format = StreamFormat.detect(from: stream)
                return format != .hls && format != .dash
            }
            .sorted { (s1: Stream, s2: Stream) -> Bool in
                // Preferred quality first
                let s1Preferred: Bool = s1.resolution == maxRes
                let s2Preferred: Bool = s2.resolution == maxRes
                if s1Preferred != s2Preferred {
                    return s1Preferred
                }
                // Then by resolution (higher first)
                let res1: StreamResolution = s1.resolution ?? .p360
                let res2: StreamResolution = s2.resolution ?? .p360
                if res1 != res2 {
                    return res1 > res2
                }
                // Within same resolution, better codec ranks higher
                return videoCodecPriority(s1.videoCodec) > videoCodecPriority(s2.videoCodec)
            }

        // When advanced details are hidden, show only best stream per resolution
        if !showAdvancedStreamDetails {
            var bestByResolution: [Int: Stream] = [:]
            for stream in onlineVideoStreams {
                let height: Int = stream.resolution?.height ?? 0
                if let existing = bestByResolution[height] {
                    if videoCodecPriority(stream.videoCodec) > videoCodecPriority(existing.videoCodec) {
                        bestByResolution[height] = stream
                    }
                } else {
                    bestByResolution[height] = stream
                }
            }
            let sortedOnlineStreams: [Stream] = bestByResolution.values.sorted { (s1: Stream, s2: Stream) -> Bool in
                let s1Preferred: Bool = s1.resolution == maxRes
                let s2Preferred: Bool = s2.resolution == maxRes
                if s1Preferred != s2Preferred {
                    return s1Preferred
                }
                return (s1.resolution ?? .p360) > (s2.resolution ?? .p360)
            }
            return downloadedStreams + sortedOnlineStreams
        }

        return downloadedStreams + onlineVideoStreams
    }

    var recommendedVideoStreams: [Stream] {
        let allowSoftware = allowSoftwareDecodedFormats
        return videoStreams.filter { (stream: Stream) -> Bool in
            if stream.url.isFileURL { return true }
            // Media-source streams (no resolution metadata, nil codec) must
            // not be misclassified as software-decoded and hidden
            if stream.resolution == nil { return true }
            if stream.isMuxed { return true }
            if allowSoftware { return true }
            return !requiresSoftwareDecode(stream.videoCodec)
        }
    }

    var otherVideoStreams: [Stream] {
        if allowSoftwareDecodedFormats { return [] }
        return videoStreams.filter { (stream: Stream) -> Bool in
            if stream.url.isFileURL { return false }
            if stream.resolution == nil { return false }
            if stream.isMuxed { return false }
            return requiresSoftwareDecode(stream.videoCodec)
        }
    }

    /// Audio-only streams, deduplicated and sorted with preferred language first.
    var audioStreams: [Stream] {
        let allAudio: [Stream] = streams.filter { $0.isAudioOnly }

        // When advanced details are hidden, show only best stream per language
        if !showAdvancedStreamDetails {
            var bestByLanguage: [String: Stream] = [:]

            for stream in allAudio {
                let lang: String = stream.audioLanguage ?? ""
                if let existing = bestByLanguage[lang] {
                    if audioCodecPriority(stream.audioCodec) > audioCodecPriority(existing.audioCodec) {
                        bestByLanguage[lang] = stream
                    } else if audioCodecPriority(stream.audioCodec) == audioCodecPriority(existing.audioCodec) {
                        if (stream.bitrate ?? 0) > (existing.bitrate ?? 0) {
                            bestByLanguage[lang] = stream
                        }
                    }
                } else {
                    bestByLanguage[lang] = stream
                }
            }

            return bestByLanguage.values.sorted { (s1: Stream, s2: Stream) -> Bool in
                if let preferred = preferredAudioLanguage {
                    let lang1: String = s1.audioLanguage ?? ""
                    let lang2: String = s2.audioLanguage ?? ""
                    let s1Preferred: Bool = lang1.hasPrefix(preferred)
                    let s2Preferred: Bool = lang2.hasPrefix(preferred)
                    if s1Preferred != s2Preferred {
                        return s1Preferred
                    }
                } else {
                    if s1.isOriginalAudio != s2.isOriginalAudio {
                        return s1.isOriginalAudio
                    }
                }
                return (s1.audioLanguage ?? "") < (s2.audioLanguage ?? "")
            }
        }

        // Full details mode: Group by language + codec
        var bestByKey: [String: Stream] = [:]

        for stream in allAudio {
            let lang: String = stream.audioLanguage ?? ""
            let codec: String = stream.audioCodec ?? ""
            let key: String = "\(lang)|\(codec)"

            if let existing = bestByKey[key] {
                if (stream.bitrate ?? 0) > (existing.bitrate ?? 0) {
                    bestByKey[key] = stream
                }
            } else {
                bestByKey[key] = stream
            }
        }

        return bestByKey.values.sorted { (s1: Stream, s2: Stream) -> Bool in
            // Preferred language or original audio first
            if let preferred = preferredAudioLanguage {
                let lang1: String = s1.audioLanguage ?? ""
                let lang2: String = s2.audioLanguage ?? ""
                let s1Preferred: Bool = lang1.hasPrefix(preferred)
                let s2Preferred: Bool = lang2.hasPrefix(preferred)
                if s1Preferred != s2Preferred {
                    return s1Preferred
                }
            } else {
                if s1.isOriginalAudio != s2.isOriginalAudio {
                    return s1.isOriginalAudio
                }
            }

            // Then by language alphabetically
            let lang1: String = s1.audioLanguage ?? ""
            let lang2: String = s2.audioLanguage ?? ""
            if lang1 != lang2 {
                return lang1 < lang2
            }

            // Better codec ranks higher
            let codec1: Int = audioCodecPriority(s1.audioCodec)
            let codec2: Int = audioCodecPriority(s2.audioCodec)
            if codec1 != codec2 {
                return codec1 > codec2
            }

            // Higher bitrate first
            return (s1.bitrate ?? 0) > (s2.bitrate ?? 0)
        }
    }

    /// Whether we need to show audio selection (video-only streams exist).
    var hasVideoOnlyStreams: Bool {
        videoStreams.contains { $0.isVideoOnly }
    }

    /// Best audio stream for auto-selection based on preferred language setting.
    var defaultAudioStream: Stream? {
        if let preferred = preferredAudioLanguage {
            if let preferredStream = audioStreams.first(where: { ($0.audioLanguage ?? "").hasPrefix(preferred) }) {
                return preferredStream
            }
        }

        if let originalStream = audioStreams.first(where: { $0.isOriginalAudio }) {
            return originalStream
        }

        return audioStreams.first
    }

    /// Captions sorted with preferred language first.
    var sortedCaptions: [Caption] {
        captions.sorted { (c1: Caption, c2: Caption) -> Bool in
            if let preferred = preferredSubtitlesLanguage {
                let c1Preferred: Bool = c1.baseLanguageCode == preferred || c1.languageCode.hasPrefix(preferred)
                let c2Preferred: Bool = c2.baseLanguageCode == preferred || c2.languageCode.hasPrefix(preferred)
                if c1Preferred != c2Preferred {
                    return c1Preferred
                }
            }
            return c1.displayName.localizedCaseInsensitiveCompare(c2.displayName) == .orderedAscending
        }
    }

    // MARK: - Codec Helpers

    /// Returns codec priority for sorting (higher = better).
    func videoCodecPriority(_ codec: String?) -> Int {
        HardwareCapabilities.shared.codecPriority(for: codec)
    }

    /// Returns audio codec priority for sorting.
    func audioCodecPriority(_ codec: String?) -> Int {
        guard let codec = codec?.lowercased() else { return 0 }
        if codec.contains("opus") || codec.contains("aac") || codec.contains("mp4a") {
            return 1
        }
        return 0
    }

    /// Whether a codec requires software decoding.
    func requiresSoftwareDecode(_ codec: String?) -> Bool {
        HardwareCapabilities.shared.codecPriority(for: codec) == 0
    }

    // MARK: - Audio Track Parsing

    /// Parses audio track information from a stream.
    func parseAudioTrackName(_ stream: Stream) -> AudioTrackInfo {
        let isAutoDubbed: Bool = stream.audioTrackName?.contains("Auto-dubbed") == true
        let isOriginal: Bool = stream.audioTrackName?.contains("Original") == true

        let trackType: String?
        if isAutoDubbed {
            trackType = "AD"
        } else if isOriginal {
            trackType = "ORIGINAL"
        } else {
            trackType = nil
        }

        let language: String
        if let lang = stream.audioLanguage {
            let fullName: String = Locale.current.localizedString(forLanguageCode: lang) ?? lang.uppercased()
            language = shortenRegionName(fullName)
        } else if let name = stream.audioTrackName {
            let cleaned: String = name
                .replacingOccurrences(of: "(Auto-dubbed)", with: "")
                .replacingOccurrences(of: "(Original)", with: "")
                .trimmingCharacters(in: .whitespaces)
            language = shortenRegionName(cleaned)
        } else {
            language = String(localized: "stream.audio.default")
        }

        return AudioTrackInfo(language: language, trackType: trackType)
    }

    /// Shortens region names in language strings.
    private func shortenRegionName(_ name: String) -> String {
        let regionMappings: [String: String] = [
            "(United States)": "(US)",
            "(United Kingdom)": "(UK)",
            "(Germany)": "",
            "(France)": "",
            "(Spain)": "",
            "(Italy)": "",
            "(Japan)": "",
            "(China)": "",
            "(Brazil)": "",
            "(Portugal)": "",
            "(Russia)": "",
            "(Korea)": ""
        ]

        var shortened: String = name
        for (full, short) in regionMappings {
            shortened = shortened.replacingOccurrences(of: full, with: short)
        }
        return shortened.trimmingCharacters(in: .whitespaces)
    }

    // MARK: - Rate Helpers

    /// Returns the previous playback rate, or nil if at minimum.
    func previousRate() -> PlaybackRate? {
        let allRates: [PlaybackRate] = PlaybackRate.allCases
        guard let currentIndex = allRates.firstIndex(of: currentRate),
              currentIndex > 0 else {
            return nil
        }
        return allRates[currentIndex - 1]
    }

    /// Returns the next playback rate, or nil if at maximum.
    func nextRate() -> PlaybackRate? {
        let allRates: [PlaybackRate] = PlaybackRate.allCases
        guard let currentIndex = allRates.firstIndex(of: currentRate),
              currentIndex < allRates.count - 1 else {
            return nil
        }
        return allRates[currentIndex + 1]
    }

    // MARK: - Formatting Helpers

    /// Formats a file size from bytes.
    func formatFileSize(_ bytes: Int64) -> String {
        ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
    }

    /// Formats stream details (bitrate and file size).
    func formatStreamDetails(bitrate: Int?, fileSize: String?) -> String {
        var parts: [String] = []
        if let bitrate {
            parts.append(formatBitrate(bitrate))
        }
        if let fileSize {
            parts.append(fileSize)
        }
        return parts.joined(separator: " · ")
    }

    /// Formats audio details (track type, bitrate, file size).
    func formatAudioDetails(trackType: String?, bitrate: Int?, fileSize: String?) -> String {
        var parts: [String] = []
        if let trackType {
            parts.append(trackType)
        }
        if let bitrate {
            parts.append(formatBitrate(bitrate))
        }
        if let fileSize {
            parts.append(fileSize)
        }
        return parts.joined(separator: " · ")
    }

    /// Formats a codec string for display.
    func formatCodec(_ codec: String) -> String {
        let lowercased: String = codec.lowercased()
        if lowercased.contains("avc") || lowercased.contains("h264") {
            return "H.264"
        } else if lowercased.contains("hev") || lowercased.contains("h265") || lowercased.contains("hevc") {
            return "HEVC"
        } else if lowercased.contains("vp9") || lowercased.contains("vp09") {
            return "VP9"
        } else if lowercased.contains("av1") || lowercased.contains("av01") {
            return "AV1"
        }
        return codec.uppercased()
    }

    /// Returns a color for a codec badge.
    func codecColor(_ codec: String) -> Color {
        let lowercased: String = codec.lowercased()
        if lowercased.contains("av1") || lowercased.contains("av01") {
            return .blue
        } else if lowercased.contains("vp9") || lowercased.contains("vp09") {
            return .orange
        } else if lowercased.contains("avc") || lowercased.contains("h264") {
            return .red
        } else if lowercased.contains("hev") || lowercased.contains("h265") || lowercased.contains("hevc") {
            return .green
        }
        return .gray
    }

    /// Formats a bitrate for display.
    func formatBitrate(_ bitrate: Int) -> String {
        if bitrate >= 1_000_000 {
            return String(format: "%.1f Mbps", Double(bitrate) / 1_000_000)
        } else {
            return "\(bitrate / 1000) kbps"
        }
    }
}
