//
//  CashierView.swift
//  Padel Tournament
//
//  Created by Razmig Sarkissian on 04/03/2023.
//

import SwiftUI
import Combine

struct ShareableObject {

    let cashierViewModel: CashierViewModel
    let players: [PlayerRegistration]
    let fileName: String

    func sharedData() async -> Data? {
        let _players = players.filter({ cashierViewModel._shouldDisplayPlayer($0) })
            .map {
                [$0.pasteData()]
                    .compacted()
                    .joined(separator: "\n")
            }
            .joined(separator: "\n\n")
        return _players.data(using: .utf8)
    }
}

extension ShareableObject: Transferable {
    enum ShareError: Error {
        case failed
    }
    
    static var transferRepresentation: some TransferRepresentation {
        let rep = DataRepresentation<ShareableObject>(exportedContentType: .plainText) { object in
            guard let data = await object.sharedData() else {
                throw ShareError.failed
            }
            return data
        }
        return rep.suggestedFileName { object in object.fileName }
    }
}

extension Array {
    func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>, order: SortOrder) -> [Element] {
        switch order {
        case .ascending:
            return self.sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
        case .descending:
            return self.sorted { $0[keyPath: keyPath] > $1[keyPath: keyPath] }
        }
    }
}

class CashierViewModel: ObservableObject {
    let id: UUID = UUID()
    @Published var sortOption: SortOption = .callDate
    @Published var filterOption: FilterOption = .all
    @Published var sortOrder: SortOrder = .ascending
    @Published var searchText: String  = ""
    @Published var isSearching: Bool = false
    
    func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool {
        team.unsortedPlayers().anySatisfy({
            _shouldDisplayPlayer($0)
        })
    }

    func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
        if searchText.isEmpty == false {
            sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) && player.contains(searchText)
        } else {
            sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player)
        }
    }
    
    func _sortPlayers(_ players: [PlayerRegistration]) -> [PlayerRegistration] {
        players
            .filter({ _shouldDisplayPlayer($0) })
            .sorted { sortOption.compare($0, $1, order: sortOrder) }
    }
    
    enum SortOption: Int, Identifiable, CaseIterable {
        case teamRank
        case alphabeticalLastName
        case alphabeticalFirstName
        case playerRank
        case age
        case callDate
        
        var sortingKeyPath: AnyKeyPath {
              switch self {
              case .alphabeticalLastName:
                  return \PlayerRegistration.lastName
              case .alphabeticalFirstName:
                  return \PlayerRegistration.firstName
              case .playerRank, .teamRank, .callDate:
                  return \PlayerRegistration.computedRank
              case .age:
                  return \PlayerRegistration.computedAge!
              }
          }

          func compare(_ lhs: PlayerRegistration, _ rhs: PlayerRegistration, order: SortOrder) -> Bool {
              switch self {
              case .alphabeticalLastName:
                  return compare(lhs[keyPath: \.lastName], rhs[keyPath: \.lastName], order: order)
              case .alphabeticalFirstName:
                  return compare(lhs[keyPath: \.firstName], rhs[keyPath: \.firstName], order: order)
              case .playerRank, .teamRank, .callDate:
                  return compare(lhs[keyPath: \.computedRank], rhs[keyPath: \.computedRank], order: order)
              case .age:
                  return compare(lhs[keyPath: \.computedAge!], rhs[keyPath: \.computedAge!], order: order)
              }
          }

          private func compare<T: Comparable>(_ lhs: T, _ rhs: T, order: SortOrder) -> Bool {
              switch order {
              case .ascending:
                  return lhs < rhs
              case .descending:
                  return lhs > rhs
              }
          }

        func shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
            switch self {
            case .age:
                player.computedAge != nil
            default:
                true
            }
        }
        
        var id: Int { self.rawValue }
        func localizedLabel() -> String {
            switch self {
            case .callDate:
                return "Convocation"
            case .teamRank:
                return "Poids d'équipe"
            case .alphabeticalLastName:
                return "Nom"
            case .alphabeticalFirstName:
                return "Prénom"
            case .playerRank:
                return "Rang"
            case .age:
                return "Âge"
            }
        }
    }
    
    enum FilterOption: Int, Identifiable, CaseIterable  {
        case all
        case didPay
        case didNotPay
        
        var id: Int { self.rawValue }
        
        func localizedLabel() -> String {
            switch self {
            case .all:
                return "Tous"
            case .didPay:
                return "Réglé"
            case .didNotPay:
                return "Non réglé"
            }
        }
        
        func shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
            switch self {
            case .all:
                return true
            case .didPay:
                return player.hasPaid()
            case .didNotPay:
                return player.hasPaid() == false

            }
        }
    }

}

struct CashierView: View {
    @EnvironmentObject var dataStore: DataStore
    @EnvironmentObject var cashierViewModel: CashierViewModel

    var tournaments : [Tournament]
    @State private var teams: [TeamRegistration]
    @State private var players: [PlayerRegistration]
    @State private var shareableObject: ShareableObject?
    
    init(tournament: Tournament, teams: [TeamRegistration]) {
        self.tournaments = [tournament]
        _teams = .init(wrappedValue: teams)
        _players = .init(wrappedValue: teams.flatMap({ $0.unsortedPlayers() }))
    }
    
    var body: some View {
        List {
            if cashierViewModel.isSearching == false {
                Section {
                    Picker(selection: $cashierViewModel.filterOption) {
                        ForEach(CashierViewModel.FilterOption.allCases) { filterOption in
                            Text(filterOption.localizedLabel()).tag(filterOption)
                        }
                    } label: {
                        Text("Statut du règlement")
                    }
                    
                    Picker(selection: $cashierViewModel.sortOption) {
                        ForEach(CashierViewModel.SortOption.allCases) { sortOption in
                            Text(sortOption.localizedLabel()).tag(sortOption)
                        }
                    } label: {
                        Text("Affichage par")
                    }
                    
                    Picker(selection: $cashierViewModel.sortOrder) {
                        Text("Croissant").tag(SortOrder.ascending)
                        Text("Décroissant").tag(SortOrder.descending)
                    } label: {
                        Text("Trier par ordre")
                    }
                } header: {
                    Text("Options d'affichage")
                }
            }

            let filteredPlayers = cashierViewModel._sortPlayers(players)
            if filteredPlayers.isEmpty {
                _contentUnavailableView()
            }
            
            switch cashierViewModel.sortOption {
            case .teamRank:
                TeamRankView(teams: teams, displayTournamentTitle: tournaments.count > 1)
            case .alphabeticalLastName, .alphabeticalFirstName, .playerRank, .age:
                PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1)
            case .callDate:
                let _teams = teams.filter({ $0.callDate != nil })
                TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1)
            }
        }
        .onAppear {
            cashierViewModel.searchText = ""
//            if tournaments.count == 1 {
//                if tournaments.first!.hasEnded() == true, tournaments.first!.players().anySatisfy({ $0.hasPaid() == false }) {
//                    filterOption = .didNotPay
//                }
//            }
            if cashierViewModel.sortOption == .callDate && teams.first(where: { $0.callDate != nil }) == nil {
                cashierViewModel.sortOption = .teamRank
            }
            self.shareableObject = ShareableObject(cashierViewModel: cashierViewModel, players: players, fileName: "Encaissement")
        }
        .onChange(of: cashierViewModel.sortOrder) {
            teams = cashierViewModel.sortOrder == .ascending ? teams : teams.reversed()
        }
        .headerProminence(.increased)
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                if let shareableObject {
                    ShareLink(
                        item: shareableObject,
                        preview: SharePreview(shareableObject.fileName)
                    )
                }
            }
        }
    }
    
    struct PlayerCashierView: View {
        @EnvironmentObject var cashierViewModel: CashierViewModel
        let players: [PlayerRegistration]
        let displayTournamentTitle: Bool
        
        var body: some View {
            ForEach(players) { player in
                Section {
                    EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
                } header: {
                    if displayTournamentTitle, let tournamentTitle = player.tournament()?.tournamentTitle() {
                        Text(tournamentTitle)
                    }
                } footer: {
                    if let teamCallDate = player.team()?.callDate {
                        Text("équipe convoqué") + Text(teamCallDate.localizedDate())
                    }
                }
            }
        }
    }
    
    struct TeamRankView: View {
        @EnvironmentObject var cashierViewModel: CashierViewModel
        let teams: [TeamRegistration]
        let displayTournamentTitle: Bool
        
        var body: some View {
            ForEach(teams) { team in
                let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) })
                if players.isEmpty == false {
                    Section {
                        ForEach(players) { player in
                            EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
                        }
                    } header: {
                        if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
                            Text(tournamentTitle)
                        }
                    } footer: {
                        if let callDate = team.callDate {
                            Text("convoqué") + Text(callDate.localizedDate())
                        }
                    }
                }
            }
        }
    }


    struct TeamCallDateView: View {
        @EnvironmentObject var cashierViewModel: CashierViewModel
        let teams: [TeamRegistration]
        let displayTournamentTitle: Bool
        
        var body: some View {
            let groupedTeams = Dictionary(grouping: teams) { team in
                team.callDate
            }
            let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed()

            ForEach(keys, id: \.self) { key in
                if let _teams = groupedTeams[key] {
                    ForEach(_teams) { team in
                        let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) })
                        if players.isEmpty == false {
                            Section {
                                ForEach(players) { player in
                                    EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
                                }
                            } header: {
                                Text(key.localizedDate())
                            } footer: {
                                if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
                                    Text(tournamentTitle)
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private func _unavailableIcon() -> String {
        switch cashierViewModel.sortOption {
        case .teamRank, .callDate:
            return "person.2.slash.fill"
        default:
            return "person.slash.fill"
        }
    }

    @ViewBuilder
    private func _contentUnavailableView() -> some View {
        if cashierViewModel.isSearching {
            ContentUnavailableView.search(text: cashierViewModel.searchText)
        } else {
            ContentUnavailableView("Aucun résultat", systemImage: _unavailableIcon())
        }
    }
}
