//
//  PlanningView.swift
//  PadelClub
//
//  Created by Razmig Sarkissian on 07/04/2024.
//

import SwiftUI
import LeStorage
import TipKit

struct PlanningView: View {
    
    @EnvironmentObject var dataStore: DataStore
    @Environment(Tournament.self) var tournament: Tournament
    @State private var selectedDay: Date?
    @Binding var selectedScheduleDestination: ScheduleDestination?
    @State private var filterOption: PlanningFilterOption = .byDefault
    @State private var showFinishedMatches: Bool = false
    @State private var enableMove: Bool = false
    
    let allMatches: [Match]
    let timeSlotMoveOptionTip = TimeSlotMoveOptionTip()
    
    init(matches: [Match], selectedScheduleDestination: Binding<ScheduleDestination?>) {
        self.allMatches = matches
        _selectedScheduleDestination = selectedScheduleDestination
    }
    
    var matches: [Match] {
        allMatches.filter({ showFinishedMatches || $0.endDate == nil })
    }
        
    var timeSlots: [Date:[Match]] {
        Dictionary(grouping: matches) { $0.startDate ?? .distantFuture }
    }
    
    func days(timeSlots: [Date:[Match]]) -> [Date] {
        Set(timeSlots.keys.map { $0.startOfDay }).sorted()
    }
    
    func keys(timeSlots: [Date:[Match]]) -> [Date] {
        timeSlots.keys.sorted()
    }
        
    private func _computedTitle(days: [Date]) -> String {
        if let selectedDay {
            return selectedDay.formatted(.dateTime.day().weekday().month())
        } else {
            if days.count > 1 {
                return "Tous les jours"
            } else {
                return "Horaires"
            }
        }
    }
    
    var body: some View {
        let timeSlots = self.timeSlots
        let keys = self.keys(timeSlots: timeSlots)
        let days = self.days(timeSlots: timeSlots)
        let matches = matches
        let notSlots = matches.allSatisfy({ $0.startDate == nil })
        BySlotView(days: days, keys: keys, timeSlots: timeSlots, matches: matches, selectedDay: selectedDay)
            .environment(\.filterOption, filterOption)
            .environment(\.showFinishedMatches, showFinishedMatches)
            .environment(\.enableMove, enableMove)
            .navigationTitle(Text(_computedTitle(days: days)))
            .navigationBarBackButtonHidden(enableMove)
            .toolbar(content: {
                if days.count > 1 {
                    ToolbarTitleMenu {
                        Picker(selection: $selectedDay) {
                            Text("Tous les jours").tag(nil as Date?)
                            ForEach(days, id: \.self) { day in
                                if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
                                    Text("Sans horaire").tag(day as Date?)
                                } else {
                                    Text(day.formatted(.dateTime.day().weekday().month())).tag(day as Date?)
                                }
                            }
                        } label: {
                            Text("Jour")
                        }
                        .pickerStyle(.automatic)
                        .disabled(enableMove)
                    }
                }
                
                if enableMove {
                    ToolbarItem(placement: .topBarLeading) {
                        Button("Annuler") {
                            enableMove = false
                        }
                    }
                    
                    ToolbarItem(placement: .topBarTrailing) {
                        Button("Sauver") {
                            do {
                                try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches)
                            } catch {
                                Logger.error(error)
                            }

                            enableMove = false
                        }
                    }

                } else {
                    
                    ToolbarItemGroup(placement: .topBarTrailing) {
                        if notSlots == false {
                            Toggle(isOn: $enableMove) {
                                Label("Déplacer", systemImage: "rectangle.2.swap")
                            }
                            .popoverTip(timeSlotMoveOptionTip)
                        }
                        
                        Menu {
                            Section {
                                Picker(selection: $showFinishedMatches) {
                                    Text("Afficher tous les matchs").tag(true)
                                    Text("Masquer les matchs terminés").tag(false)
                                } label: {
                                    Text("Option de filtrage")
                                }
                                .labelsHidden()
                                .pickerStyle(.inline)
                            } header: {
                                Text("Option de filtrage")
                            }
                            
                            Divider()
                            
                            Section {
                                Picker(selection: $filterOption) {
                                    ForEach(PlanningFilterOption.allCases) {
                                        Text($0.localizedPlanningLabel()).tag($0)
                                    }
                                } label: {
                                    Text("Option de triage")
                                }
                                .labelsHidden()
                                .pickerStyle(.inline)
                            } header: {
                                Text("Option de triage")
                                
                            }
                        } label: {
                            Label("Trier", systemImage: "line.3.horizontal.decrease.circle")
                                .symbolVariant(filterOption == .byCourt || showFinishedMatches ? .fill : .none)
                        }
                        
                    }
                }
            })
            .overlay {
                if notSlots {
                    ContentUnavailableView {
                        Label("Aucun horaire défini", systemImage: "clock.badge.questionmark")
                    } description: {
                        Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi")
                    } actions: {
                        RowButtonView("Horaire intelligent") {
                            selectedScheduleDestination = nil
                        }
                    }
                }
            }
    }
    
    struct BySlotView: View {
        @Environment(Tournament.self) var tournament: Tournament
        @Environment(\.filterOption) private var filterOption
        @Environment(\.showFinishedMatches) private var showFinishedMatches
        @Environment(\.enableMove) private var enableMove

        let days: [Date]
        let keys: [Date]
        let timeSlots: [Date: [Match]]
        let matches: [Match]
        let selectedDay: Date?
        let timeSlotMoveTip = TimeSlotMoveTip()

        var body: some View {
            List {
                
                if enableMove {
                    TipView(timeSlotMoveTip)
                        .tipStyle(tint: .logoYellow, asSection: true)
                }
                
                if !matches.allSatisfy({ $0.startDate == nil }) {
                    ForEach(days.filter({ selectedDay == nil || selectedDay == $0 }), id: \.self) { day in
                        DaySectionView(
                            day: day,
                            keys: keys.filter({ $0.dayInt == day.dayInt }),
                            timeSlots: timeSlots,
                            selectedDay: selectedDay
                        )
                    }
                }
            }
        }
    }


    struct DaySectionView: View {
        @Environment(Tournament.self) var tournament: Tournament
        @Environment(\.filterOption) private var filterOption
        @Environment(\.showFinishedMatches) private var showFinishedMatches
        @Environment(\.enableMove) private var enableMove

        let day: Date
        let keys: [Date]
        let timeSlots: [Date: [Match]]
        let selectedDay: Date?

        var body: some View {
            Section {
                ForEach(keys, id: \.self) { key in
                    TimeSlotSectionView(
                        key: key,
                        matches: timeSlots[key]?.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting) ?? []
                    )
                }
                .onMove(perform: enableMove ? moveSection : nil)
            } header: {
                HeaderView(day: day, timeSlots: timeSlots)
            } footer: {
                if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
                    Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.")
                }
            }
        }
        
        func moveSection(from source: IndexSet, to destination: Int) {
            let daySlots = keys.filter { $0.dayInt == day.dayInt }.sorted()
             
            guard let sourceIdx = source.first,
                sourceIdx < daySlots.count,
                destination <= daySlots.count else {
                return
            }
             
             // Create a mutable copy of the time slots for this day
            var slotsToUpdate = daySlots
             
            let updateRange = min(sourceIdx, destination)...max(sourceIdx, destination)

            // Perform the move in the array
            let sourceTime = slotsToUpdate.remove(at: sourceIdx)
            if sourceIdx < destination {
                slotsToUpdate.insert(sourceTime, at: destination - 1)
            } else {
                slotsToUpdate.insert(sourceTime, at: destination)
            }
                                     
            // Update matches by swapping their startDates
            for index in updateRange {
                // Find the new time slot for these matches
                guard let oldStartTime = slotsToUpdate[safe: index] else { continue }
                guard let newStartTime = daySlots[safe: index] else { continue }
                guard let matchesToUpdate = timeSlots[oldStartTime] else { continue }

                 // Update each match with the new start time
                for match in matchesToUpdate {
                    match.startDate = newStartTime
                }
            }
        }
    }


    struct TimeSlotSectionView: View {
        @Environment(\.enableMove) private var enableMove
        let key: Date
        let matches: [Match]

        var body: some View {
            if !matches.isEmpty {
                if enableMove {
                    TimeSlotHeaderView(key: key, matches: matches)
                } else {
                    DisclosureGroup {
                        MatchListView(matches: matches)
                    } label: {
                        TimeSlotHeaderView(key: key, matches: matches)
                    }
                }
            }
        }
    }

    struct MatchListView: View {
        let matches: [Match]

        var body: some View {
            ForEach(matches) { match in
                NavigationLink {
                    MatchDetailView(match: match)
                        .matchViewStyle(.sectionedStandardStyle)
                } label: {
                    MatchRowView(match: match)
                }
            }
        }
    }

    struct MatchRowView: View {
        let match: Match

        var body: some View {
            LabeledContent {
                if let courtName = match.courtName() {
                    Text(courtName)
                }
            } label: {
                if let groupStage = match.groupStageObject {
                    Text(groupStage.groupStageTitle(.title))
                } else if let round = match.roundObject {
                    Text(round.roundTitle())
                }
                Text(match.matchTitle())
            }
        }
    }


    struct HeaderView: View {
        @Environment(\.filterOption) private var filterOption
        @Environment(\.showFinishedMatches) private var showFinishedMatches
        @Environment(\.enableMove) private var enableMove

        let day: Date
        let timeSlots: [Date: [Match]]

        var body: some View {
            HStack {
                if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
                    Text("Sans horaire")
                } else {
                    Text(day.formatted(.dateTime.day().weekday().month()))
                }
                Spacer()
                let count = _matchesCount(inDayInt: day.dayInt, timeSlots: timeSlots)
                if showFinishedMatches {
                    Text(_formattedMatchCount(count))
                } else {
                    Text("\(_formattedMatchCount(count)) restant\(count.pluralSuffix)")
                }
            }
        }

        private func _matchesCount(inDayInt dayInt: Int, timeSlots: [Date: [Match]]) -> Int {
            timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count
        }

        private func _formattedMatchCount(_ count: Int) -> String {
            return "\(count.formatted()) match\(count.pluralSuffix)"
        }
    }

    struct TimeSlotHeaderView: View {
        let key: Date
        let matches: [Match]
        @Environment(Tournament.self) var tournament: Tournament

        var body: some View {
            LabeledContent {
                Text("\(matches.count.formatted()) match\(matches.count.pluralSuffix)")
            } label: {
                if key.monthYearFormatted == Date.distantFuture.monthYearFormatted {
                    Text("Aucun horaire")
                } else {
                    Text(key.formatted(date: .omitted, time: .shortened))
                        .font(.title)
                        .fontWeight(.semibold)
                }
                
                if matches.count <= tournament.courtCount {
                    let names = matches.sorted(by: \.computedOrder)
                        .compactMap({ $0.roundTitle() })
                        .reduce(into: [String]()) { uniqueNames, name in
                            if !uniqueNames.contains(name) {
                                uniqueNames.append(name)
                            }
                        }
                    Text(names.joined(separator: ", ")).lineLimit(1).truncationMode(.tail)
                } else {
                    Text(matches.count.formatted().appending(" matchs"))
                }

            }
        }
    }

    
//    struct BySlotView: View {
//        @Environment(Tournament.self) var tournament: Tournament
//        let days: [Date]
//        let keys: [Date]
//        let timeSlots: [Date:[Match]]
//        let matches: [Match]
//        let selectedDay: Date?
//        let filterOption: PlanningFilterOption
//        let showFinishedMatches: Bool
//
//        var body: some View {
//            List {
//                if matches.allSatisfy({ $0.startDate == nil }) == false {
//                    ForEach(days.filter({ selectedDay == nil || selectedDay == $0 }), id: \.self) { day in
//                        Section {
//                            ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in
//                                if let _matches = timeSlots[key]?.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting) {
//                                    DisclosureGroup {
//                                        ForEach(_matches) { match in
//                                            NavigationLink {
//                                                MatchDetailView(match: match)
//                                                    .matchViewStyle(.sectionedStandardStyle)
//
//                                            } label: {
//                                                LabeledContent {
//                                                    if let courtName = match.courtName() {
//                                                        Text(courtName)
//                                                    }
//                                                } label: {
//                                                    if let groupStage = match.groupStageObject {
//                                                        Text(groupStage.groupStageTitle(.title))
//                                                    } else if let round = match.roundObject {
//                                                        Text(round.roundTitle())
//                                                    }
//                                                    Text(match.matchTitle())
//                                                }
//                                            }
//                                        }
//                                    } label: {
//                                        _timeSlotView(key: key, matches: _matches)
//                                    }
//                                }
//                            }
//                            .onMove(perform: moveSection)
//                        } header: {
//                            HStack {
//                                if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
//                                    Text("Sans horaire")
//                                } else {
//                                    Text(day.formatted(.dateTime.day().weekday().month()))
//                                }
//                                Spacer()
//                                let count = _matchesCount(inDayInt: day.dayInt, timeSlots: timeSlots)
//                                if showFinishedMatches {
//                                    Text(self._formattedMatchCount(count))
//                                } else {
//                                    Text(self._formattedMatchCount(count) + " restant\(count.pluralSuffix)")
//                                }
//                            }
//                        } footer: {
//                            if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
//                                Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.")
//                            }
//                        }
//                        .headerProminence(.increased)
//                    }
//                }
//            }
//        }
//        
//        func moveSection(from source: IndexSet, to destination: Int) {
//            let daySlots = keys.filter { selectedDay == nil || $0.dayInt == selectedDay?.dayInt }.sorted()
//             
//            guard let sourceIdx = source.first,
//                sourceIdx < daySlots.count,
//                destination <= daySlots.count else {
//                return
//            }
//             
//             // Create a mutable copy of the time slots for this day
//            var slotsToUpdate = daySlots
//             
//            let updateRange = min(sourceIdx, destination)...max(sourceIdx, destination) - 1
//            print(updateRange)
//
//            // Perform the move in the array
//            let sourceTime = slotsToUpdate.remove(at: sourceIdx)
//            if sourceIdx < destination {
//                slotsToUpdate.insert(sourceTime, at: destination - 1)
//            } else {
//                slotsToUpdate.insert(sourceTime, at: destination)
//            }
//                                     
//            // Update matches by swapping their startDates
//            for index in updateRange {
//                // Find the new time slot for these matches
//                let oldStartTime = slotsToUpdate[index]
//                let newStartTime = daySlots[index]
//                guard let matchesToUpdate = timeSlots[oldStartTime] else { continue }
//                print("moving", oldStartTime, "to", newStartTime)
//
//                 // Update each match with the new start time
//                for match in matchesToUpdate {
//                    match.startDate = newStartTime
//                }
//            }
//            
//            try? self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
//        }
//
//        
//        private func _matchesCount(inDayInt dayInt: Int, timeSlots: [Date:[Match]]) -> Int {
//            timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count
//        }
//        
//        private func _timeSlotView(key: Date, matches: [Match]) -> some View {
//            LabeledContent {
//                Text(self._formattedMatchCount(matches.count))
//            } label: {
//                if key.monthYearFormatted == Date.distantFuture.monthYearFormatted {
//                    Text("Aucun horaire")
//                } else {
//                    Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold)
//                }
//                if matches.count <= tournament.courtCount {
//                    let names = matches.sorted(by: \.computedOrder)
//                        .compactMap({ $0.roundTitle() })
//                        .reduce(into: [String]()) { uniqueNames, name in
//                            if !uniqueNames.contains(name) {
//                                uniqueNames.append(name)
//                            }
//                        }
//                    Text(names.joined(separator: ", ")).lineLimit(1).truncationMode(.tail)
//                } else {
//                    Text(matches.count.formatted().appending(" matchs"))
//                }
//            }
//        }
//        
//        fileprivate func _formattedMatchCount(_ count: Int) -> String {
//            return "\(count.formatted()) match\(count.pluralSuffix)"
//        }
//    }
}

enum PlanningFilterOption: Int, CaseIterable, Identifiable {
    var id: Int { self.rawValue }
    
    case byDefault
    case byCourt
    
    func localizedPlanningLabel() -> String {
        switch self {
        case .byCourt:
            return "Par terrain"
        case .byDefault:
            return "Par ordre des matchs"
        }
    }
}


struct FilterOptionKey: EnvironmentKey {
    static let defaultValue: PlanningFilterOption = .byDefault
}

extension EnvironmentValues {
    var filterOption: PlanningFilterOption {
        get { self[FilterOptionKey.self] }
        set { self[FilterOptionKey.self] = newValue }
    }
}

struct ShowFinishedMatchesKey: EnvironmentKey {
    static let defaultValue: Bool = false
}

extension EnvironmentValues {
    var showFinishedMatches: Bool {
        get { self[ShowFinishedMatchesKey.self] }
        set { self[ShowFinishedMatchesKey.self] = newValue }
    }
}

struct EnableMoveKey: EnvironmentKey {
    static let defaultValue: Bool = false
}

extension EnvironmentValues {
    var enableMove: Bool {
        get { self[EnableMoveKey.self] }
        set { self[EnableMoveKey.self] = newValue }
    }
}
