//
//  CheatBaseView.swift
//  Delta
//
//  Created by Riley Testut on 1/17/23.
//  Copyright © 2023 Riley Testut. All rights reserved.
//

import SwiftUI

import enum SQLite.Result

@available(iOS 14, *)
extension CheatBaseView
{
    private class ViewModel: ObservableObject
    {
        @Published
        var allCheats: [CheatMetadata]? {
            didSet {
                guard let cheats = allCheats else {
                    self.cheatsByCategory = nil
                    return
                }
                
                let cheatsByCategory = Dictionary(grouping: cheats, by: { $0.category }).sorted { $0.key.id < $1.key.id }
                self.cheatsByCategory = cheatsByCategory
            }
        }
        
        @Published
        private(set) var cheatsByCategory: [(CheatCategory, [CheatMetadata])]?
        
        @Published
        private(set) var error: Error?
        
        @Published
        var searchText: String = "" {
            didSet {
                self.searchCheats()
            }
        }
        
        @Published
        private(set) var filteredCheats: [CheatMetadata]?
        
        @MainActor
        func fetchCheats(for game: Game) async
        {
            guard self.allCheats == nil else { return }
            
            do
            {
                let database = try CheatBase()                
                let cheats = try await database.cheats(for: game) ?? []
                self.allCheats = cheats
            }
            catch
            {
                self.error = error
            }
        }
        
        private func searchCheats()
        {
            if let cheats = self.allCheats, !self.searchText.isEmpty
            {
                let predicate = NSPredicate(forSearchingForText: self.searchText, inValuesForKeyPaths: [#keyPath(CheatMetadata.name), #keyPath(CheatMetadata.cheatDescription)])
                
                let filteredCheats = cheats.filter { predicate.evaluate(with: $0) }
                self.filteredCheats = filteredCheats
            }
            else
            {
                self.filteredCheats = nil
            }
        }
    }
}

@available(iOS 14, *)
struct CheatBaseView: View
{
    let game: Game?
    
    var cancellationHandler: (() -> Void)?
    var selectionHandler: ((CheatMetadata) -> Void)?
    
    @StateObject
    private var viewModel = ViewModel()
    
    @State
    private var activationHintCheat: CheatMetadata?
    
    var body: some View {
        NavigationView {
            ZStack {
                if let cheats = viewModel.allCheats, !cheats.isEmpty
                {
                    // Only show List if there is at least one cheat for this game.
                    cheatList()
                }
                
                // Place above List
                placeholderView()
            }
            .alert(item: $activationHintCheat) { cheat in
                Alert(title: Text("How to Activate"),
                      message: Text(cheat.activationHint ?? ""),
                      dismissButton: .default(Text("OK")))
            }
            .navigationTitle(Text(game?.name ?? "CheatBase"))
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") {
                        cancellationHandler?()
                    }
                }
            }
        }
        .onAppear {
            Task {
                guard let game = self.game else { return }
                await viewModel.fetchCheats(for: game)
            }
        }
    }
    
    private func cheatList() -> some View
    {
        VStack {
            if #unavailable(iOS 15)
            {
                LegacySearchBar(text: $viewModel.searchText)
            }
            
            let listView = List {
                if let filteredCheats = viewModel.filteredCheats
                {
                    ForEach(filteredCheats) { cheat in
                        cell(for: cheat)
                    }
                }
                else if let cheats = viewModel.cheatsByCategory
                {
                    ForEach(cheats, id: \.0.id) { (category, cheats) in
                        Section {
                            DisclosureGroup {
                                ForEach(cheats) { cheat in
                                    cell(for: cheat)
                                }
                            } label: {
                                Text(category.name)
                            }
                        } footer: {
                            Text(category.categoryDescription)
                        }
                    }
                }
            }
            
            if #available(iOS 15, *)
            {
                listView.searchable(text: $viewModel.searchText)
            }
            else
            {
                listView
            }
        }
        .listStyle(.insetGrouped)
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)
    }

    
    private func cell(for cheat: CheatMetadata) -> some View
    {
        ZStack(alignment: .leading) {
            Button(action: { choose(cheat) }) {}
            
            HStack {
                // Name + Description
                VStack(alignment: .leading, spacing: 4) {
                    Text(cheat.name)
                    
                    if let description = cheat.cheatDescription
                    {
                        Text(description)
                            .font(.caption)
                    }
                }
                
                // Activation Hint
                if cheat.activationHint != nil
                {
                    Spacer()
                    
                    Button(action: { activationHintCheat = cheat }) {
                        Image(systemName: "info.circle")
                    }
                    .buttonStyle(.borderless)
                }
            }
            .multilineTextAlignment(.leading)
        }
    }

    private func placeholderView() -> some View
    {
        VStack(spacing: 8) {
            if let error = viewModel.error
            {
                Text("Unable to Load Cheats")
                    .font(.title)
                
                if let error = error as? SQLite.Result
                {
                    // SQLite.Result implements CustomStringConvertible.description, but not localizedDescription.
                    Text(String(describing: error))
                        .font(.callout)
                }
                else
                {
                    Text(error.localizedDescription)
                        .font(.callout)
                }
            }
            else if let filteredCheats = viewModel.filteredCheats, filteredCheats.isEmpty
            {
                Text("Cheat Not Found")
                    .font(.title)
                
                Text("Please make sure the name is correct, or try searching for another cheat.")
                    .font(.callout)
            }
            else if let cheats = viewModel.allCheats, cheats.isEmpty
            {
                Text("No Cheats")
                    .font(.title)
                
                Text("There are no cheats for this game in Delta's CheatBase. Please try a different game.")
                    .font(.callout)
            }
            else if viewModel.allCheats == nil
            {
                ProgressView()
                    .progressViewStyle(.circular)
            }
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
        .foregroundColor(.gray)
        .padding()
    }
    
    init(game: Game, cheats: [CheatMetadata]? = nil)
    {
        self.game = game
        
        let viewModel = ViewModel()
        viewModel.allCheats = cheats
        self._viewModel = StateObject(wrappedValue: viewModel)
    }
}

@available(iOS 14, *)
private extension CheatBaseView
{
    func choose(_ cheatMetadata: CheatMetadata)
    {
        self.selectionHandler?(cheatMetadata)
    }
}
