import Foundation

/// Look up the ICAO aircraft type designator (e.g. "B738", "A21N") for a
/// given 24-bit ICAO transponder address.
///
/// Backed by `aircraftDB.json` — slimmed copy of OpenSky's aircraft
/// metadata, ~100k commercial-class entries, ~1.5MB on disk. Loads on
/// first access and stays in memory for the rest of the session.
///
/// Used to power the "Aircraft type" filter on the live map. The ADS-B
/// emitter category that OpenSky's anonymous `/states/all` returns is
/// almost always null, so the type designator from the DB is what we
/// surface to the user.
final class AircraftDatabase: @unchecked Sendable {
    static let shared = AircraftDatabase()

    /// Storage. Empty until `preload()` has filled it. Reads before preload
    /// return nil — the type-code filter will simply look empty until the
    /// DB has loaded, instead of blocking the main thread for ~100-200ms
    /// while parsing 1.5MB of JSON.
    private let lock = NSLock()
    private var byICAO24: [String: String] = [:]
    private var isLoaded: Bool = false

    private init() {}

    /// Kick off the JSON parse on a background thread. Safe to call
    /// multiple times — subsequent calls are no-ops. Call this once at
    /// app launch (FlightsApp.init) so the data is ready by the time
    /// the user opens the Live tab.
    func preload() {
        lock.lock()
        if isLoaded {
            lock.unlock()
            return
        }
        isLoaded = true
        lock.unlock()

        Task.detached(priority: .utility) { [weak self] in
            guard let url = Bundle.main.url(forResource: "aircraftDB", withExtension: "json"),
                  let data = try? Data(contentsOf: url),
                  let decoded = try? JSONDecoder().decode([String: String].self, from: data)
            else { return }
            self?.lock.withLock {
                self?.byICAO24 = decoded
            }
        }
    }

    /// Returns the ICAO aircraft type designator for the given 24-bit
    /// ICAO transponder address, or nil if the airframe isn't in the DB
    /// (or the DB hasn't finished loading yet).
    func typeCode(forICAO24 icao24: String) -> String? {
        let key = icao24.lowercased()
        lock.lock()
        defer { lock.unlock() }
        return byICAO24[key]
    }

    /// Friendly display name for an ICAO type designator, e.g.
    /// "B738" → "Boeing 737-800". Falls back to the raw code when not in
    /// the table.
    func displayName(forTypeCode code: String) -> String {
        Self.typeNames[code.uppercased()] ?? code
    }

    /// Normalize either an IATA aircraft code (e.g. "73H") or an ICAO
    /// type designator (e.g. "B738") to the ICAO form the rest of the
    /// app expects. Schedule feeds (route-explorer) hand out IATA;
    /// FR24's live feed and FlightAware both hand out ICAO. We want
    /// one canonical form on disk.
    func normalizedICAO(forCode code: String) -> String {
        let upper = code.uppercased()
        if Self.typeNames[upper] != nil { return upper }  // already ICAO
        return Self.iataToICAO[upper] ?? upper
    }

    /// Common IATA → ICAO mappings for aircraft codes we'll actually
    /// see from schedule data. Not exhaustive — covers the bulk of
    /// commercial fleet types. Anything missing falls through as-is.
    private static let iataToICAO: [String: String] = [
        // Airbus narrowbody
        "319": "A319", "31N": "A19N",
        "320": "A320", "32A": "A320", "32B": "A320", "32N": "A20N", "32S": "A320",
        "321": "A321", "32Q": "A21N",
        "318": "A318",
        // Airbus widebody
        "330": "A332", "332": "A332", "333": "A333", "338": "A338", "339": "A339",
        "340": "A343", "343": "A343", "346": "A346",
        "350": "A359", "359": "A359", "35K": "A35K", "351": "A359", "358": "A359",
        "380": "A388", "388": "A388",
        // A220
        "221": "BCS1", "223": "BCS3",
        // Boeing 737 family
        "73G": "B737", "73R": "B737",
        "73H": "B738", "73W": "B738", "738": "B738",
        "73J": "B739", "739": "B739", "73Y": "B739",
        "732": "B732", "733": "B733", "734": "B734", "735": "B735", "736": "B736",
        "7M7": "B37M", "7M8": "B38M", "7M9": "B39M", "7MJ": "B3XM",
        // Boeing 747/767/777/787
        "744": "B744", "748": "B748",
        "762": "B762", "763": "B763", "764": "B764",
        "772": "B772", "773": "B773", "77L": "B77L", "77W": "B77W", "77F": "B77F",
        "778": "B778", "779": "B779",
        "788": "B788", "789": "B789", "78X": "B78X", "78J": "B78X",
        // 757
        "752": "B752", "753": "B753",
        // Embraer regional
        "E70": "E170", "E75": "E175", "E7W": "E175",
        "E90": "E190", "E95": "E195", "295": "E295",
        // Bombardier / CRJ
        "CR2": "CRJ2", "CR7": "CRJ7", "CR9": "CRJ9",
        // Dash 8
        "DH4": "DH8D", "DH3": "DH8C",
        // ATR
        "AT5": "AT45", "AT7": "AT72", "ATR": "AT72",
        // MD-80 family
        "M80": "MD80", "M81": "MD81", "M82": "MD82", "M83": "MD83", "M87": "MD87", "M88": "MD88", "M90": "MD90",
    ]

    /// Friendly names for the ~150 most common commercial type designators
    /// we'd see on the map. Anything else displays as the raw 3–4 letter
    /// code (still useful for filtering). This is by ICAO Doc 8643.
    private static let typeNames: [String: String] = [
        // Airbus narrowbody
        "A318": "Airbus A318",
        "A319": "Airbus A319",
        "A320": "Airbus A320",
        "A321": "Airbus A321",
        "A19N": "Airbus A319neo",
        "A20N": "Airbus A320neo",
        "A21N": "Airbus A321neo",

        // Airbus widebody
        "A30B": "Airbus A300",
        "A306": "Airbus A300-600",
        "A310": "Airbus A310",
        "A332": "Airbus A330-200",
        "A333": "Airbus A330-300",
        "A337": "Airbus A330-700 BelugaXL",
        "A338": "Airbus A330-800neo",
        "A339": "Airbus A330-900neo",
        "A342": "Airbus A340-200",
        "A343": "Airbus A340-300",
        "A345": "Airbus A340-500",
        "A346": "Airbus A340-600",
        "A359": "Airbus A350-900",
        "A35K": "Airbus A350-1000",
        "A388": "Airbus A380",

        // A220 / CSeries
        "BCS1": "Airbus A220-100",
        "BCS3": "Airbus A220-300",

        // Boeing narrowbody
        "B712": "Boeing 717",
        "B721": "Boeing 727",
        "B722": "Boeing 727-200",
        "B731": "Boeing 737-100",
        "B732": "Boeing 737-200",
        "B733": "Boeing 737-300",
        "B734": "Boeing 737-400",
        "B735": "Boeing 737-500",
        "B736": "Boeing 737-600",
        "B737": "Boeing 737-700",
        "B738": "Boeing 737-800",
        "B739": "Boeing 737-900",
        "B37M": "Boeing 737 MAX 7",
        "B38M": "Boeing 737 MAX 8",
        "B39M": "Boeing 737 MAX 9",
        "B3XM": "Boeing 737 MAX 10",
        "B752": "Boeing 757-200",
        "B753": "Boeing 757-300",

        // Boeing widebody
        "B741": "Boeing 747-100",
        "B742": "Boeing 747-200",
        "B743": "Boeing 747-300",
        "B744": "Boeing 747-400",
        "B748": "Boeing 747-8",
        "B74F": "Boeing 747 Freighter",
        "B762": "Boeing 767-200",
        "B763": "Boeing 767-300",
        "B764": "Boeing 767-400",
        "B772": "Boeing 777-200",
        "B77L": "Boeing 777-200LR",
        "B773": "Boeing 777-300",
        "B77W": "Boeing 777-300ER",
        "B77F": "Boeing 777F",
        "B778": "Boeing 777-8",
        "B779": "Boeing 777-9",
        "B788": "Boeing 787-8",
        "B789": "Boeing 787-9",
        "B78X": "Boeing 787-10",

        // Embraer regional
        "E135": "Embraer ERJ-135",
        "E140": "Embraer ERJ-140",
        "E145": "Embraer ERJ-145",
        "E170": "Embraer E170",
        "E175": "Embraer E175",
        "E190": "Embraer E190",
        "E195": "Embraer E195",
        "E290": "Embraer E190-E2",
        "E295": "Embraer E195-E2",

        // Bombardier / Mitsubishi regional
        "CRJ1": "CRJ-100",
        "CRJ2": "CRJ-200",
        "CRJ7": "CRJ-700",
        "CRJ9": "CRJ-900",
        "CRJX": "CRJ-1000",
        "MRJ":  "Mitsubishi SpaceJet",

        // De Havilland / Dash
        "DH8A": "Dash 8-100",
        "DH8B": "Dash 8-200",
        "DH8C": "Dash 8-300",
        "DH8D": "Dash 8 Q400",

        // ATR
        "AT43": "ATR 42-300",
        "AT45": "ATR 42-500",
        "AT46": "ATR 42-600",
        "AT72": "ATR 72-200",
        "AT75": "ATR 72-500",
        "AT76": "ATR 72-600",

        // Business jets
        "BE20": "Beechcraft King Air 200",
        "BE40": "Beechjet 400",
        "BE9L": "King Air 90",
        "B190": "Beechcraft 1900",
        "B350": "King Air 350",
        "CL30": "Bombardier Challenger 300",
        "CL60": "Bombardier Challenger 600",
        "CL65": "Bombardier Challenger 650",
        "GLEX": "Bombardier Global Express",
        "GL5T": "Bombardier Global 5000",
        "GLF4": "Gulfstream IV",
        "GLF5": "Gulfstream V",
        "GLF6": "Gulfstream G650",
        "G280": "Gulfstream G280",
        "FA10": "Dassault Falcon 10",
        "FA20": "Dassault Falcon 20",
        "FA50": "Dassault Falcon 50",
        "FA7X": "Dassault Falcon 7X",
        "FA8X": "Dassault Falcon 8X",
        "C25A": "Cessna Citation CJ2",
        "C25B": "Cessna Citation CJ3",
        "C25C": "Cessna Citation CJ4",
        "C56X": "Cessna Citation Excel",
        "C680": "Cessna Citation Sovereign",
        "C68A": "Cessna Citation Latitude",
        "C700": "Cessna Citation Longitude",
        "PC12": "Pilatus PC-12",
        "PC24": "Pilatus PC-24",
        "PRM1": "Hawker Premier",

        // Helicopters
        "AS32": "Eurocopter Super Puma",
        "AS50": "Eurocopter Squirrel",
        "AS65": "Eurocopter Dauphin",
        "EC20": "Eurocopter EC120",
        "EC25": "Eurocopter EC225",
        "EC30": "Eurocopter EC130",
        "EC35": "Eurocopter EC135",
        "EC45": "Eurocopter EC145",
        "EC55": "Eurocopter EC155",
        "EC75": "Eurocopter EC175",
        "H125": "Airbus H125",
        "H135": "Airbus H135",
        "H145": "Airbus H145",
        "H160": "Airbus H160",
        "H225": "Airbus H225",
        "B06":  "Bell 206",
        "B407": "Bell 407",
        "B412": "Bell 412",
        "B429": "Bell 429",
        "S70":  "Sikorsky S-70",
        "S76":  "Sikorsky S-76",
        "S92":  "Sikorsky S-92",

        // Misc / cargo classic
        "MD80": "MD-80",
        "MD81": "MD-81",
        "MD82": "MD-82",
        "MD83": "MD-83",
        "MD87": "MD-87",
        "MD88": "MD-88",
        "MD90": "MD-90",
        "MD11": "MD-11",
        "DC10": "DC-10",
        "DC9":  "DC-9",
        "B717": "Boeing 717",
        "F70":  "Fokker 70",
        "F100": "Fokker 100"
    ]
}
