import Foundation

public struct MenuEntry: Sendable, Equatable {
    public let raw: JSONObject

    public var id: String? { raw["id"]?.stringValue }
    public var type: String? { raw["type"]?.stringValue }
    public var title: String? {
        if let direct = Self.nonEmpty(raw["title"]?.stringValue) {
            return direct
        }
        if let info = raw["info"]?.objectValue,
           let value = Self.nonEmpty(info["title"]?.stringValue) {
            return value
        }
        if let i18nInfo = raw["i18n_info"]?.objectValue,
           let value = Self.localizedTitle(from: i18nInfo) {
            return value
        }
        if let i18n = raw["i18n"]?.objectValue,
           let value = Self.localizedTitle(from: i18n) {
            return value
        }
        return nil
    }
    public var url: String? { raw["url"]?.stringValue }

    private static func localizedTitle(from map: JSONObject) -> String? {
        if let direct = nonEmpty(map["title"]?.stringValue) {
            return direct
        }

        for language in preferredLanguageKeys {
            if let exact = map[language], let title = extractTitle(from: exact) {
                return title
            }
            if let matched = map.first(where: { $0.key.lowercased() == language.lowercased() })?.value,
               let title = extractTitle(from: matched) {
                return title
            }
        }

        for value in map.values {
            if let title = extractTitle(from: value) {
                return title
            }
        }
        return nil
    }

    private static func extractTitle(from value: JSONValue) -> String? {
        if let text = nonEmpty(value.stringValue) {
            return text
        }
        guard let object = value.objectValue else { return nil }
        if let title = nonEmpty(object["title"]?.stringValue) {
            return title
        }
        if let name = nonEmpty(object["name"]?.stringValue) {
            return name
        }
        return nil
    }

    private static func nonEmpty(_ value: String?) -> String? {
        guard let value else { return nil }
        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
        return trimmed.isEmpty ? nil : trimmed
    }

    private static let preferredLanguageKeys: [String] = {
        var out: [String] = []
        var seen = Set<String>()

        func appendUnique(_ value: String) {
            guard !value.isEmpty, !seen.contains(value) else { return }
            seen.insert(value)
            out.append(value)
        }

        for localeIdentifier in Locale.preferredLanguages {
            if let code = Locale(identifier: localeIdentifier).language.languageCode?.identifier {
                appendUnique(code)
                appendUnique(code.uppercased())
            }
            let short = String(localeIdentifier.prefix(2))
            if !short.isEmpty {
                appendUnique(short.lowercased())
                appendUnique(short.uppercased())
            }
        }
        for fallback in ["cs", "sk", "en", "CS", "SK", "EN"] {
            appendUnique(fallback)
        }
        return out
    }()
}

public struct StreamOption: Sendable, Equatable {
    public let raw: JSONObject

    public var id: String? { raw["id"]?.stringValue }
    public var provider: String? { raw["provider"]?.stringValue }
    public var url: String? { raw["url"]?.stringValue }
    public var ident: String? { raw["ident"]?.stringValue }
    public var quality: String? { raw["quality"]?.stringValue }
    public var lang: String? { raw["lang"]?.stringValue }
    public var subtitlePath: String? { raw["subs"]?.stringValue }
    public var bitrate: Int {
        raw["bitrate"]?.intValue ?? 0
    }
    public var streamInfo: JSONObject? {
        raw["stream_info"]?.objectValue
    }
    public var videoCodec: String? {
        streamInfo?["video"]?.objectValue?["codec"]?.stringValue
    }
    public var videoWidth: Int? {
        streamInfo?["video"]?.objectValue?["width"]?.intValue
    }
    public var videoHeight: Int? {
        streamInfo?["video"]?.objectValue?["height"]?.intValue
    }
    public var source: String? {
        streamInfo?["src"]?.stringValue
    }
    public var group: String? {
        streamInfo?["grp"]?.stringValue
    }
    public var filename: String? {
        streamInfo?["filename"]?.stringValue
    }
    public var hasHDR: Bool {
        streamInfo?["HDR"]?.boolValue ?? false
    }
    public var hasDolbyVision: Bool {
        streamInfo?["DV"]?.boolValue ?? false
    }
    public var hasAtmos: Bool {
        (streamInfo?["Atmos"]?.boolValue ?? false) || (streamInfo?["atmos"]?.boolValue ?? false)
    }
}

public struct StreamCinemaResponse: Sendable, Decodable {
    public let raw: JSONObject
    public let system: JSONObject?
    public let filter: JSONObject?
    public let menu: [MenuEntry]?
    public let streams: [StreamOption]?

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let raw = try container.decode(JSONObject.self)
        self.raw = raw
        self.system = raw["system"]?.objectValue
        self.filter = raw["filter"]?.objectValue
        if let menuRaw = raw["menu"]?.arrayValue {
            self.menu = menuRaw.compactMap { value in
                guard let object = value.objectValue else { return nil }
                return MenuEntry(raw: object)
            }
        } else {
            self.menu = nil
        }
        if let streamsRaw = raw["strms"]?.arrayValue {
            self.streams = streamsRaw.compactMap { value in
                guard let object = value.objectValue else { return nil }
                return StreamOption(raw: object)
            }
        } else {
            self.streams = nil
        }
    }
}

public enum QueryValue: Sendable, Equatable {
    case string(String)
    case int(Int)
    case bool(Bool)
    case array([String])

    var values: [String] {
        switch self {
        case .string(let value):
            return [value]
        case .int(let value):
            return [String(value)]
        case .bool(let value):
            return [value ? "1" : "0"]
        case .array(let values):
            return values
        }
    }
}
