// swiftlint:disable switch_case_on_newline
import Defaults

enum Country: String, CaseIterable, Identifiable, Hashable, Defaults.Serializable {
    var id: String {
        rawValue
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    case dz = "DZ"
    case ar = "AR"
    case au = "AU"
    case at = "AT"
    case az = "AZ"
    case bh = "BH"
    case bd = "BD"
    case by = "BY"
    case be = "BE"
    case bo = "BO"
    case ba = "BA"
    case br = "BR"
    case bg = "BG"
    case ca = "CA"
    case cl = "CL"
    case co = "CO"
    case cr = "CR"
    case hr = "HR"
    case cy = "CY"
    case cz = "CZ"
    case dk = "DK"
    case `do` = "DO"
    case ec = "EC"
    case eg = "EG"
    case sv = "SV"
    case ee = "EE"
    case fi = "FI"
    case fr = "FR"
    case ge = "GE"
    case de = "DE"
    case gh = "GH"
    case gr = "GR"
    case gt = "GT"
    case hn = "HN"
    case hk = "HK"
    case hu = "HU"
    case `is` = "IS"
    case `in` = "IN"
    case id = "ID"
    case iq = "IQ"
    case ie = "IE"
    case il = "IL"
    case it = "IT"
    case jm = "JM"
    case jp = "JP"
    case jo = "JO"
    case kz = "KZ"
    case ke = "KE"
    case kr = "KR"
    case kw = "KW"
    case lv = "LV"
    case lb = "LB"
    case ly = "LY"
    case li = "LI"
    case lt = "LT"
    case lu = "LU"
    case mk = "MK"
    case my = "MY"
    case mt = "MT"
    case mx = "MX"
    case me = "ME"
    case ma = "MA"
    case np = "NP"
    case nl = "NL"
    case nz = "NZ"
    case ni = "NI"
    case ng = "NG"
    case no = "NO"
    case om = "OM"
    case pk = "PK"
    case pa = "PA"
    case pg = "PG"
    case py = "PY"
    case pe = "PE"
    case ph = "PH"
    case pl = "PL"
    case pt = "PT"
    case pr = "PR"
    case qa = "QA"
    case ro = "RO"
    case ru = "RU"
    case sa = "SA"
    case sn = "SN"
    case rs = "RS"
    case sg = "SG"
    case sk = "SK"
    case si = "SI"
    case za = "ZA"
    case es = "ES"
    case lk = "LK"
    case se = "SE"
    case ch = "CH"
    case tw = "TW"
    case tz = "TZ"
    case th = "TH"
    case tn = "TN"
    case tr = "TR"
    case ug = "UG"
    case ua = "UA"
    case ae = "AE"
    case gb = "GB"
    case us = "US"
    case uy = "UY"
    case ve = "VE"
    case vn = "VN"
    case vi = "VI"
    case ye = "YE"
    case zw = "ZW"
}

extension Country {
    var name: String {
        switch self {
        case .dz: return "Algeria"
        case .ar: return "Argentina"
        case .au: return "Australia"
        case .at: return "Austria"
        case .az: return "Azerbaijan"
        case .bh: return "Bahrain"
        case .bd: return "Bangladesh"
        case .by: return "Belarus"
        case .be: return "Belgium"
        case .bo: return "Bolivia (Plurinational State of)"
        case .ba: return "Bosnia and Herzegovina"
        case .br: return "Brazil"
        case .bg: return "Bulgaria"
        case .ca: return "Canada"
        case .cl: return "Chile"
        case .co: return "Colombia"
        case .cr: return "Costa Rica"
        case .hr: return "Croatia"
        case .cy: return "Cyprus"
        case .cz: return "Czechia"
        case .dk: return "Denmark"
        case .do: return "Dominican Republic"
        case .ec: return "Ecuador"
        case .eg: return "Egypt"
        case .sv: return "El Salvador"
        case .ee: return "Estonia"
        case .fi: return "Finland"
        case .fr: return "France"
        case .ge: return "Georgia"
        case .de: return "Germany"
        case .gh: return "Ghana"
        case .gr: return "Greece"
        case .gt: return "Guatemala"
        case .hn: return "Honduras"
        case .hk: return "Hong Kong"
        case .hu: return "Hungary"
        case .is: return "Iceland"
        case .in: return "India"
        case .id: return "Indonesia"
        case .iq: return "Iraq"
        case .ie: return "Ireland"
        case .il: return "Israel"
        case .it: return "Italy"
        case .jm: return "Jamaica"
        case .jp: return "Japan"
        case .jo: return "Jordan"
        case .kz: return "Kazakhstan"
        case .ke: return "Kenya"
        case .kr: return "Korea (Republic of)"
        case .kw: return "Kuwait"
        case .lv: return "Latvia"
        case .lb: return "Lebanon"
        case .ly: return "Libya"
        case .li: return "Liechtenstein"
        case .lt: return "Lithuania"
        case .lu: return "Luxembourg"
        case .mk: return "Macedonia (the former Yugoslav Republic of)"
        case .my: return "Malaysia"
        case .mt: return "Malta"
        case .mx: return "Mexico"
        case .me: return "Montenegro"
        case .ma: return "Morocco"
        case .np: return "Nepal"
        case .nl: return "Netherlands"
        case .nz: return "New Zealand"
        case .ni: return "Nicaragua"
        case .ng: return "Nigeria"
        case .no: return "Norway"
        case .om: return "Oman"
        case .pk: return "Pakistan"
        case .pa: return "Panama"
        case .pg: return "Papua New Guinea"
        case .py: return "Paraguay"
        case .pe: return "Peru"
        case .ph: return "Philippines"
        case .pl: return "Poland"
        case .pt: return "Portugal"
        case .pr: return "Puerto Rico"
        case .qa: return "Qatar"
        case .ro: return "Romania"
        case .ru: return "Russian Federation"
        case .sa: return "Saudi Arabia"
        case .sn: return "Senegal"
        case .rs: return "Serbia"
        case .sg: return "Singapore"
        case .sk: return "Slovakia"
        case .si: return "Slovenia"
        case .za: return "South Africa"
        case .es: return "Spain"
        case .lk: return "Sri Lanka"
        case .se: return "Sweden"
        case .ch: return "Switzerland"
        case .tw: return "Taiwan, Province of China[a]"
        case .tz: return "Tanzania, United Republic of"
        case .th: return "Thailand"
        case .tn: return "Tunisia"
        case .tr: return "Turkey"
        case .ug: return "Uganda"
        case .ua: return "Ukraine"
        case .ae: return "United Arab Emirates"
        case .gb: return "United Kingdom of Great Britain and Northern Ireland"
        case .us: return "United States of America"
        case .uy: return "Uruguay"
        case .ve: return "Venezuela (Bolivarian Republic of)"
        case .vn: return "Viet Nam"
        case .vi: return "Virgin Islands (U.S.)"
        case .ye: return "Yemen"
        case .zw: return "Zimbabwe"
        }
    }

    var flag: String {
        let unicodeScalars = rawValue
            .unicodeScalars
            .map { $0.value + 0x1F1E6 - 65 }
            .compactMap(UnicodeScalar.init)
        var result = ""
        result.unicodeScalars.append(contentsOf: unicodeScalars)
        return result
    }

    static func search(_ query: String) -> [Country] {
        if let country = searchByCode(query) {
            return [country]
        }

        let countries = filteredCountries { stringFolding($0) == stringFolding(query) }

        return countries.isEmpty ? searchByPartialName(query) : countries
    }

    static func searchByCode(_ code: String) -> Country? {
        Country(rawValue: code.uppercased())
    }

    static func searchByPartialName(_ name: String) -> [Country] {
        guard !name.isEmpty else {
            return []
        }

        return filteredCountries { stringFolding($0).contains(stringFolding(name)) }
    }

    private static func stringFolding(_ string: String) -> String {
        string.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
    }

    private static func filteredCountries(_ predicate: (String) -> Bool) -> [Country] {
        Country.allCases
            .map { $0.name }
            .filter(predicate)
            .compactMap { string in Country.allCases.first { $0.name == string } }
    }
}