//
//  MediaFile.swift
//  Yattee
//
//  Represents a file or folder from a media source.
//

import Foundation

/// Represents a file or folder from a media source.
struct MediaFile: Identifiable, Hashable, Sendable {
    /// Unique identifier combining source and path.
    var id: String { "\(source.id):\(path)" }

    /// The media source this file belongs to.
    let source: MediaSource

    /// Full path from the source root.
    let path: String

    /// Display name of the file/folder.
    let name: String

    /// Whether this is a directory.
    let isDirectory: Bool
    
    /// Whether this is an SMB share (top-level directory on SMB server).
    let isShare: Bool

    /// File size in bytes (nil for directories).
    let size: Int64?

    /// Last modified date.
    let modifiedDate: Date?

    /// Creation date.
    let createdDate: Date?

    /// MIME type if known.
    let mimeType: String?

    // MARK: - Initialization

    init(
        source: MediaSource,
        path: String,
        name: String,
        isDirectory: Bool,
        isShare: Bool = false,
        size: Int64? = nil,
        modifiedDate: Date? = nil,
        createdDate: Date? = nil,
        mimeType: String? = nil
    ) {
        self.source = source
        self.path = path
        self.name = name
        self.isDirectory = isDirectory
        self.isShare = isShare
        self.size = size
        self.modifiedDate = modifiedDate
        self.createdDate = createdDate
        self.mimeType = mimeType
    }

    // MARK: - Computed Properties

    /// Full URL to this file.
    var url: URL {
        // For local folders, the persisted `source.url` may point at a stale app
        // container path after iOS reinstall/restore. Prefer the freshly resolved
        // bookmark URL stored in `LocalFolderURLResolver` when available.
        if source.type == .localFolder,
           let resolved = LocalFolderURLResolver.resolvedURL(for: source.id) {
            return resolved.appendingPathComponent(path)
        }
        return source.url.appendingPathComponent(path)
    }

    /// File extension (lowercase).
    var fileExtension: String {
        (name as NSString).pathExtension.lowercased()
    }

    /// Whether this file is a video.
    var isVideo: Bool {
        guard !isDirectory else { return false }
        return Self.videoExtensions.contains(fileExtension)
    }

    /// Whether this file is an audio file.
    var isAudio: Bool {
        guard !isDirectory else { return false }
        return Self.audioExtensions.contains(fileExtension)
    }

    /// Whether this file is playable media.
    var isPlayable: Bool {
        isVideo || isAudio
    }

    /// Whether this file is a subtitle file.
    var isSubtitle: Bool {
        guard !isDirectory else { return false }
        return Self.subtitleExtensions.contains(fileExtension)
    }

    /// File name without extension.
    var baseName: String {
        (name as NSString).deletingPathExtension
    }

    /// Formatted file size string.
    var formattedSize: String? {
        // Don't show size for directories and shares
        guard !isDirectory, !isShare else { return nil }
        guard let size else { return nil }
        return ByteCountFormatter.string(fromByteCount: size, countStyle: .file)
    }

    /// System image name for the file type.
    var systemImage: String {
        if isShare {
            return "externaldrive.fill"
        }
        if isDirectory {
            return "folder.fill"
        }
        if isVideo {
            return "film"
        }
        if isAudio {
            return "music.note"
        }
        if isSubtitle {
            return "captions.bubble"
        }
        return "doc"
    }

    // MARK: - Supported Extensions

    static let videoExtensions: Set<String> = [
        "mp4", "m4v", "mov", "mkv", "avi", "webm", "wmv",
        "flv", "mpg", "mpeg", "3gp", "3gpp", "ts", "m2ts",
        "vob", "ogv", "rm", "rmvb", "asf", "divx"
    ]

    static let audioExtensions: Set<String> = [
        "mp3", "m4a", "aac", "flac", "wav", "ogg", "opus",
        "wma", "aiff", "alac", "ape"
    ]

    static let subtitleExtensions: Set<String> = [
        "srt", "vtt", "ass", "ssa", "sub"
    ]
}

// MARK: - Conversion to Video/Stream

extension MediaFile {
    /// Provider name for WebDAV media sources (syncs to iCloud).
    static let webdavProvider = "media_source_webdav"

    /// Provider name for local folder media sources (device-specific, never syncs).
    static let localFolderProvider = "media_source_local"

    /// Provider name for SMB media sources (syncs to iCloud).
    static let smbProvider = "media_source_smb"

    /// Returns the appropriate provider name based on source type.
    private var providerName: String {
        switch source.type {
        case .webdav:
            return Self.webdavProvider
        case .localFolder:
            return Self.localFolderProvider
        case .smb:
            return Self.smbProvider
        }
    }

    /// Creates a Video model for playback.
    func toVideo() -> Video {
        Video(
            id: VideoID(
                source: .extracted(extractor: providerName, originalURL: url),
                videoID: id
            ),
            title: (name as NSString).deletingPathExtension,
            description: nil,
            author: Author(id: source.id.uuidString, name: source.name),
            duration: 0, // Unknown until playback
            publishedAt: modifiedDate,
            publishedText: modifiedDate?.formatted(date: .abbreviated, time: .shortened),
            viewCount: nil,
            likeCount: nil,
            thumbnails: [], // No thumbnails for local/WebDAV files
            isLive: false,
            isUpcoming: false,
            scheduledStartTime: nil
        )
    }

    /// Creates a Stream for direct playback.
    /// - Parameter authHeaders: Optional HTTP headers for authentication.
    func toStream(authHeaders: [String: String]? = nil) -> Stream {
        Stream(
            url: url,
            resolution: nil, // Unknown
            format: fileExtension,
            httpHeaders: authHeaders
        )
    }
}

// MARK: - Preview Support

extension MediaFile {
    /// Sample media file for previews.
    static var preview: MediaFile {
        MediaFile(
            source: .webdav(name: "My NAS", url: URL(string: "https://nas.local")!),
            path: "/Movies/Sample Movie.mp4",
            name: "Sample Movie.mp4",
            isDirectory: false,
            size: 1_500_000_000,
            modifiedDate: Date().addingTimeInterval(-86400 * 7)
        )
    }

    /// Sample folder for previews.
    static var folderPreview: MediaFile {
        MediaFile(
            source: .webdav(name: "My NAS", url: URL(string: "https://nas.local")!),
            path: "/Movies",
            name: "Movies",
            isDirectory: true
        )
    }
}

// MARK: - Subtitle Matching

extension MediaFile {
    /// Finds subtitle files in the given list that match this video file.
    /// - Parameter files: List of files to search for matching subtitles.
    /// - Returns: Array of Caption objects for matching subtitle files.
    func findMatchingSubtitles(in files: [MediaFile]) -> [Caption] {
        guard isVideo else { return [] }

        let videoBaseName = baseName
        return files
            .filter { $0.isSubtitle && $0.matchesVideo(baseName: videoBaseName) }
            .compactMap { $0.toCaption(videoBaseName: videoBaseName) }
            .sorted { caption1, caption2 in
                // Sort: exact match (und) first, then alphabetically by language
                if caption1.languageCode == "und" && caption2.languageCode != "und" {
                    return true
                }
                if caption2.languageCode == "und" && caption1.languageCode != "und" {
                    return false
                }
                return caption1.displayName.localizedCaseInsensitiveCompare(caption2.displayName) == .orderedAscending
            }
    }

    /// Checks if this subtitle file matches a video with the given base name.
    private func matchesVideo(baseName videoBaseName: String) -> Bool {
        let subtitleBase = baseName

        // Exact match: video.srt for video.mp4
        if subtitleBase == videoBaseName {
            return true
        }

        // Language suffix with dot: video.en.srt, video.eng.srt, video.English.srt
        if subtitleBase.hasPrefix(videoBaseName + ".") {
            return true
        }

        // Language suffix with underscore: video_en.srt
        if subtitleBase.hasPrefix(videoBaseName + "_") {
            return true
        }

        return false
    }

    /// Converts this subtitle file to a Caption object.
    private func toCaption(videoBaseName: String) -> Caption? {
        guard isSubtitle else { return nil }

        let languageCode = extractLanguageCode(videoBaseName: videoBaseName)
        let displayName: String

        if languageCode == "und" {
            // For exact match without language code, use "Default" as label
            displayName = String(localized: "subtitles.default", defaultValue: "Default")
        } else if let localizedName = Locale.current.localizedString(forLanguageCode: languageCode) {
            displayName = localizedName
        } else {
            displayName = languageCode
        }

        return Caption(
            label: displayName,
            languageCode: languageCode,
            url: url
        )
    }

    /// Extracts the language code from this subtitle filename.
    private func extractLanguageCode(videoBaseName: String) -> String {
        let subtitleBase = baseName

        // Check for pattern: video.en.srt or video_en.srt
        let dotPrefix = videoBaseName + "."
        let underscorePrefix = videoBaseName + "_"

        var suffix: String?
        if subtitleBase.hasPrefix(dotPrefix) {
            suffix = String(subtitleBase.dropFirst(dotPrefix.count))
        } else if subtitleBase.hasPrefix(underscorePrefix) {
            suffix = String(subtitleBase.dropFirst(underscorePrefix.count))
        }

        guard let suffix, !suffix.isEmpty else {
            return "und" // undefined - exact match without language
        }

        // Normalize the language code
        return Self.normalizeLanguageCode(suffix)
    }

    /// Normalizes a language code or name to a standard 2-letter code.
    private static func normalizeLanguageCode(_ input: String) -> String {
        let lowercased = input.lowercased()

        // Map common 3-letter codes to 2-letter
        let threeToTwo: [String: String] = [
            "eng": "en", "deu": "de", "ger": "de", "fra": "fr", "fre": "fr",
            "spa": "es", "ita": "it", "por": "pt", "rus": "ru", "jpn": "ja",
            "kor": "ko", "zho": "zh", "chi": "zh", "ara": "ar", "hin": "hi",
            "pol": "pl", "nld": "nl", "dut": "nl", "swe": "sv", "nor": "no",
            "dan": "da", "fin": "fi", "tur": "tr", "ces": "cs", "cze": "cs",
            "hun": "hu", "ell": "el", "gre": "el", "heb": "he", "tha": "th",
            "vie": "vi", "ukr": "uk", "ron": "ro", "rum": "ro", "bul": "bg",
            "hrv": "hr", "slk": "sk", "slo": "sk", "slv": "sl", "srp": "sr",
            "cat": "ca", "eus": "eu", "baq": "eu", "glg": "gl", "ind": "id",
            "msa": "ms", "may": "ms", "fil": "tl", "tgl": "tl"
        ]

        if let twoLetter = threeToTwo[lowercased] {
            return twoLetter
        }

        // Map common full language names to codes
        let nameToCode: [String: String] = [
            "english": "en", "german": "de", "deutsch": "de", "french": "fr",
            "français": "fr", "francais": "fr", "spanish": "es", "español": "es",
            "espanol": "es", "italian": "it", "italiano": "it", "portuguese": "pt",
            "português": "pt", "portugues": "pt", "russian": "ru", "русский": "ru",
            "japanese": "ja", "日本語": "ja", "korean": "ko", "한국어": "ko",
            "chinese": "zh", "中文": "zh", "arabic": "ar", "العربية": "ar",
            "hindi": "hi", "हिन्दी": "hi", "polish": "pl", "polski": "pl",
            "dutch": "nl", "nederlands": "nl", "swedish": "sv", "svenska": "sv",
            "norwegian": "no", "norsk": "no", "danish": "da", "dansk": "da",
            "finnish": "fi", "suomi": "fi", "turkish": "tr", "türkçe": "tr",
            "czech": "cs", "čeština": "cs", "hungarian": "hu", "magyar": "hu",
            "greek": "el", "ελληνικά": "el", "hebrew": "he", "עברית": "he",
            "thai": "th", "ไทย": "th", "vietnamese": "vi", "tiếng việt": "vi",
            "ukrainian": "uk", "українська": "uk", "romanian": "ro", "română": "ro",
            "bulgarian": "bg", "български": "bg", "croatian": "hr", "hrvatski": "hr",
            "slovak": "sk", "slovenčina": "sk", "slovenian": "sl", "slovenščina": "sl",
            "serbian": "sr", "српски": "sr"
        ]

        if let code = nameToCode[lowercased] {
            return code
        }

        // If it's already a 2-letter code, return it
        if input.count == 2 {
            return lowercased
        }

        // Unknown - return as-is (could be a valid code we don't recognize)
        return lowercased
    }
}
