// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let federalTournament = try? JSONDecoder().decode(FederalTournament.self, from: jsonData)

import Foundation
import CoreLocation
import LeStorage

enum DayPeriod {
    case all
    case weekend
    case week
}

// MARK: - FederalTournament
struct FederalTournament: Identifiable, Codable {
    
    func getEvent() -> Event {
        let club = DataStore.shared.user.clubsObjects().first(where: { $0.code == codeClub })
        var event = DataStore.shared.events.first(where: { $0.tenupId == id.string })
        if event == nil {
            event = Event(creator: DataStore.shared.user.id, club: club?.id, name: libelle, tenupId: id.string)
            do {
                try DataStore.shared.events.addOrUpdate(instance: event!)
            } catch {
                Logger.error(error)
            }
        }
        return event!
    }
    
    static func sectionedData(from federalTournaments: [FederalTournament]) -> [String: [FederalTournament]] {
        Dictionary(grouping: federalTournaments) { federalTournament in
            URL.importDateFormatter.string(from: (federalTournament.dateDebut ?? .distantFuture))
        }
    }

    
    let id: Int
    var millesime: Int?
    var libelle: String?
    var tmc: Bool?
    var tarifAdulteChampionnat: Double?
    var type: String?
    var ageReel: Bool?
    var naturesTerrains: [JSONAny]?
    var idsArbitres: [JSONAny]?
    var tarifJeuneChampionnat: Double?
    var international, inscriptionEnLigne: Bool?
    var categorieTournoi: CategorieTournoi?
    var prixLot: Int?
    var paiementEnLigne: Bool?
    var reductionAdherentJeune, reductionAdherentAdulte: Double?
    var paiementEnLigneObligatoire: Bool?
    var villeEngagement: String?
    var senior, veteran, inscriptionEnLigneEnCours, avecResultatPublie: Bool?
    var code: String?
    var categorieAge: CategorieAge?
    var codeComite: String?
    var installations: [JSONAny]?
    var reductionEpreuveSupplementaireJeune, reductionEpreuveSupplementaireAdulte: Double?
    var nomComite: String?
    var naturesEpreuves: [Serie]?
    var jeune: Bool?
    var courrielEngagement, nomClub: String?
    var installation: Installation?
    var categorieAgeMax: CategorieAge?
    var tournoiInterne: Bool?
    var nomLigue, nomEngagement, codeLigue: String?
    var modeleDeBalle: ModeleDeBalle?
    var jugeArbitre: JugeArbitre?
    var adresse2Engagement: String?
    var epreuves: [Epreuve]?
    var dateDebut: Date?
    var serie: Serie?
    var dateFin, dateValidation: Date?
    var codePostalEngagement, codeClub: String?
    var prixEspece: Int?
    var distanceEnMetres: Double?
    
    var dayPeriod: DayPeriod {
        if let dateDebut {
            let day = dateDebut.get(.weekday)
            switch day {
            case 2...6:
                return .week
            default:
                return .weekend
            }
        }
        return .all
    }
    
    var dayDuration: Int {
        if let dateDebut, let dateFin {
            return Calendar.current.numberOfDaysBetween(dateDebut, and: dateFin) + 1
        } else {
            return 1
        }
    }
    
    var city: String? {
        if let installation { return installation.ville }
        return nil
    }
    
    var location: CLLocation? {
        if let lat = installation?.lat, let lng = installation?.lng {
            return CLLocation(latitude: lat, longitude: lng)
        }
        return nil
    }
    
    var computedStartDate: String? {
        dateDebut?.formatted(.dateTime.day(.defaultDigits).month(.abbreviated).year())
    }
            
    var tournaments: [any TournamentBuildHolder] {
        epreuves?
            .compactMap({ $0.tournamentBuild })
            .sorted(using: 
                    .keyPath(\TournamentBuild.level.order),
                    .keyPath(\TournamentBuild.category.order),
                    .keyPath(\TournamentBuild.age.order))
        ?? []
    }
    
    var shareMessage: String {
        [libelle, dateDebut?.formatted(date: .complete, time: .omitted)].compactMap({$0}).joined(separator: "\n") + "\n"
    }
    
    func validForSearch(_ searchText: String, scope: FederalTournamentSearchScope) -> Bool {
        var trimmedSearchText = searchText.lowercased().trimmingCharacters(in: .whitespaces).folding(options: .diacriticInsensitive, locale: .current)
        trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .punctuationCharacters, replacementString: " ")
        trimmedSearchText = trimmedSearchText.replaceCharactersFromSet(characterSet: .symbols, replacementString: " ")
        
        switch scope {
        case .club:
            return nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true
        case .all:
            return libelle?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomClub?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true || jugeArbitre?.nom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true ||
            jugeArbitre?.prenom?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true
        case .ligue:
            return nomLigue?.canonicalVersion.localizedCaseInsensitiveContains(trimmedSearchText) == true
        }
    }
}

extension FederalTournament: FederalTournamentHolder {
    var startDate: Date { dateDebut ?? .distantFuture }
    var endDate: Date? { dateFin }
//    var distance: Double? { distanceEnMetres }
//    var localizedLabel: String? { libelle }
//    var clubName: String? { nomClub }
//    var importedId: Int { id }
    
    var holderId: String { id.string }

    func clubLabel() -> String {
        nomClub ?? villeEngagement ?? installation?.nom ?? ""
    }

    func subtitleLabel() -> String {
        ""
    }
}

// MARK: - CategorieAge
struct CategorieAge: Codable {
    var ageJoueurMin, ageMin, ageJoueurMax, ageRechercheMax: Int?
    var categoriesAgeTypePratique: [CategoriesAgeTypePratique]?
    var ageMax: Int?
    var libelle: String?
    var id: Int?
    var valide, ageReel: Bool?
    var ageRechercheMin: Int?
    var homologable: Bool?
    
    var tournamentAge: FederalTournamentAge? {
        if let id {
            return FederalTournamentAge(rawValue: id)
        }
        if let libelle {
            return FederalTournamentAge.allCases.first(where: { $0.localizedLabel().localizedCaseInsensitiveContains(libelle) })
        }
        return nil
    }
}

// MARK: - CategoriesAgeTypePratique
struct CategoriesAgeTypePratique: Codable {
    var id: ID?
}

// MARK: - ID
struct ID: Codable {
    var typePratique: TypePratique?
    var idCategorieAge: Int?
}

enum TypePratique: String, Codable {
    case beach = "BEACH"
    case padel = "PADEL"
    case tennis = "TENNIS"
    case pickle = "PICKLE"
}

// MARK: - CategorieTournoi
struct CategorieTournoi: Codable {
    var code, codeTaxe: String?
    var compteurGda: CompteurGda?
    var libelle, niveauHierarchique: String?
    var valide: Bool?
}

// MARK: - CompteurGda
struct CompteurGda: Codable {
    var classementMax: Classement?
    var libelle: String?
    var classementMin: Classement?
}

// MARK: - Classement
struct Classement: Codable {
    var nature, libelle: String?
    var serie: Serie?
    var sexe: String?
    var id: Int?
    var valide: Bool?
    var poidsDouble, echelon: Int?
}

// MARK: - Serie
struct Serie: Codable {
    var code, libelle: String?
    var valide: Bool?
    var sexe: String?
    
    var tournamentCategory: TournamentCategory? {
        TournamentCategory.allCases.first(where: { $0.requestLabel == code })
    }
}

// MARK: - Epreuve
struct Epreuve: Codable {
    var inscriptionEnLigneEnCours: Bool?
    var categorieAge: CategorieAge?
    var typeEpreuve: TypeEpreuve?
    var tarifAdulte: Double?
    var classementHaut: Classement?
    var nombreDecoupagesPublies: Int?
    var libelle: String?
    var participationAgeReel, clotureInscriptionSansPreavis: Bool?
    var dateCloture: Date?
    var publicationResultat: Bool?
    var classementBas: Classement?
    var tarifsSpecifiques: [JSONAny]?
    var homologuee: Bool?
    var dateDebut: Date?
    var tarifJeune: Double?
    var serie: Serie?
    var categorieAgeMax: CategorieAge?
    var dateFin: Date?
    var borneAnneesNaissance: BorneAnneesNaissance?
    var natureEpreuve: Serie?
    var epreuveOpen: Bool?

    enum CodingKeys: String, CodingKey {
        case inscriptionEnLigneEnCours, categorieAge, tarifAdulte, classementHaut, nombreDecoupagesPublies, libelle, participationAgeReel, clotureInscriptionSansPreavis, dateCloture, publicationResultat, classementBas, tarifsSpecifiques, homologuee, dateDebut, tarifJeune, serie, categorieAgeMax, dateFin, borneAnneesNaissance, natureEpreuve, typeEpreuve
        case epreuveOpen = "open"
    }
    
    var tournamentBuild: TournamentBuild? {
        if let age = categorieAge?.tournamentAge, let category = natureEpreuve?.tournamentCategory, let level = typeEpreuve?.tournamentLevel {
            return TournamentBuild(category: category, level: level, age: age)
        }
        return nil
    }
}

// MARK: - TypeEpreuve
struct TypeEpreuve: Codable {
    let code: String?
    let delai: Int?
    let libelle: String?
    let coefficient, montantMax, montantMin: Int?
    let valide: Bool?
    let echelon: Int?
    let typeHomologation: String?
    
    var tournamentLevel: TournamentLevel? {
        if let code, let value = Int(code.removingFirstCharacter) {
            return TournamentLevel(rawValue: value)
        }
        return nil
    }
}

// MARK: - BorneAnneesNaissance
struct BorneAnneesNaissance: Codable {
    var min, max: Int?
}

// MARK: - Installation
struct Installation: Codable {
    var ville: String?
    var lng: Double?
    var surfaces: [JSONAny]?
    var vestiaires: Bool?
    var telephone, codePostal, nom, adresse1, adresse2: String?
    var lat: Double?
    var clubHouse: Bool?
    
    var libelle: String {
        [adresse1, adresse2, ville, codePostal].compactMap({ $0 }).joined(separator: " ")
    }
}

// MARK: - JugeArbitre
struct JugeArbitre: Codable {
    var idCRM, id: Int?
    var nom, prenom: String?

    enum CodingKeys: String, CodingKey {
        case idCRM = "idCrm"
        case id, nom, prenom
    }
}

// MARK: - ModeleDeBalle
struct ModeleDeBalle: Codable {
    var libelle: String?
    var marqueDeBalle: MarqueDeBalle?
    var id: Int?
    var valide, homologue: Bool?
}

// MARK: - MarqueDeBalle
struct MarqueDeBalle: Codable {
    var id: Int?
    var valide: Bool?
    var marque: String?
}

// MARK: - Encode/decode helpers

class JSONNull: Codable, Hashable {

    public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
        return true
    }

    public var hashValue: Int {
        return 0
    }

    public init() {}

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}

class JSONCodingKey: CodingKey {
    let key: String

    required init?(intValue: Int) {
        return nil
    }

    required init?(stringValue: String) {
        key = stringValue
    }

    var intValue: Int? {
        return nil
    }

    var stringValue: String {
        return key
    }
}

class JSONAny: Codable {

    let value: Any

    static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
        let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
        return DecodingError.typeMismatch(JSONAny.self, context)
    }

    static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
        let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
        return EncodingError.invalidValue(value, context)
    }

    static func decode(from container: SingleValueDecodingContainer) throws -> Any {
        if let value = try? container.decode(Bool.self) {
            return value
        }
        if let value = try? container.decode(Int64.self) {
            return value
        }
        if let value = try? container.decode(Double.self) {
            return value
        }
        if let value = try? container.decode(String.self) {
            return value
        }
        if container.decodeNil() {
            return JSONNull()
        }
        throw decodingError(forCodingPath: container.codingPath)
    }

    static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
        if let value = try? container.decode(Bool.self) {
            return value
        }
        if let value = try? container.decode(Int64.self) {
            return value
        }
        if let value = try? container.decode(Double.self) {
            return value
        }
        if let value = try? container.decode(String.self) {
            return value
        }
        if let value = try? container.decodeNil() {
            if value {
                return JSONNull()
            }
        }
        if var container = try? container.nestedUnkeyedContainer() {
            return try decodeArray(from: &container)
        }
        if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
            return try decodeDictionary(from: &container)
        }
        throw decodingError(forCodingPath: container.codingPath)
    }

    static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
        if let value = try? container.decode(Bool.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(Int64.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(Double.self, forKey: key) {
            return value
        }
        if let value = try? container.decode(String.self, forKey: key) {
            return value
        }
        if let value = try? container.decodeNil(forKey: key) {
            if value {
                return JSONNull()
            }
        }
        if var container = try? container.nestedUnkeyedContainer(forKey: key) {
            return try decodeArray(from: &container)
        }
        if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
            return try decodeDictionary(from: &container)
        }
        throw decodingError(forCodingPath: container.codingPath)
    }

    static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
        var arr: [Any] = []
        while !container.isAtEnd {
            let value = try decode(from: &container)
            arr.append(value)
        }
        return arr
    }

    static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
        var dict = [String: Any]()
        for key in container.allKeys {
            let value = try decode(from: &container, forKey: key)
            dict[key.stringValue] = value
        }
        return dict
    }

    static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
        for value in array {
            if let value = value as? Bool {
                try container.encode(value)
            } else if let value = value as? Int64 {
                try container.encode(value)
            } else if let value = value as? Double {
                try container.encode(value)
            } else if let value = value as? String {
                try container.encode(value)
            } else if value is JSONNull {
                try container.encodeNil()
            } else if let value = value as? [Any] {
                var container = container.nestedUnkeyedContainer()
                try encode(to: &container, array: value)
            } else if let value = value as? [String: Any] {
                var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
                try encode(to: &container, dictionary: value)
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }
    }

    static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
        for (key, value) in dictionary {
            let key = JSONCodingKey(stringValue: key)!
            if let value = value as? Bool {
                try container.encode(value, forKey: key)
            } else if let value = value as? Int64 {
                try container.encode(value, forKey: key)
            } else if let value = value as? Double {
                try container.encode(value, forKey: key)
            } else if let value = value as? String {
                try container.encode(value, forKey: key)
            } else if value is JSONNull {
                try container.encodeNil(forKey: key)
            } else if let value = value as? [Any] {
                var container = container.nestedUnkeyedContainer(forKey: key)
                try encode(to: &container, array: value)
            } else if let value = value as? [String: Any] {
                var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
                try encode(to: &container, dictionary: value)
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }
    }

    static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
        if let value = value as? Bool {
            try container.encode(value)
        } else if let value = value as? Int64 {
            try container.encode(value)
        } else if let value = value as? Double {
            try container.encode(value)
        } else if let value = value as? String {
            try container.encode(value)
        } else if value is JSONNull {
            try container.encodeNil()
        } else {
            throw encodingError(forValue: value, codingPath: container.codingPath)
        }
    }

    public required init(from decoder: Decoder) throws {
        if var arrayContainer = try? decoder.unkeyedContainer() {
            self.value = try JSONAny.decodeArray(from: &arrayContainer)
        } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
            self.value = try JSONAny.decodeDictionary(from: &container)
        } else {
            let container = try decoder.singleValueContainer()
            self.value = try JSONAny.decode(from: container)
        }
    }

    public func encode(to encoder: Encoder) throws {
        if let arr = self.value as? [Any] {
            var container = encoder.unkeyedContainer()
            try JSONAny.encode(to: &container, array: arr)
        } else if let dict = self.value as? [String: Any] {
            var container = encoder.container(keyedBy: JSONCodingKey.self)
            try JSONAny.encode(to: &container, dictionary: dict)
        } else {
            var container = encoder.singleValueContainer()
            try JSONAny.encode(to: &container, value: self.value)
        }
    }
}
