//
//  RecipeDetailView.swift
//  Nextcloud Cookbook iOS Client
//
//  Created by Vincent Meilinger on 15.09.23.
//

import Foundation
import OSLog
import SwiftUI


struct RecipeView: View {
    @EnvironmentObject var appState: AppState
    @Environment(\.dismiss) private var dismiss
    @StateObject var viewModel: ViewModel
    @GestureState private var dragOffset = CGSize.zero
    
    var imageHeight: CGFloat {
        if let image = viewModel.recipeImage {
            return image.size.height < 350 ? image.size.height : 350
        }
        return 200
    }
    
    private enum CoordinateSpaces {
        case scrollView
    }
    
    var body: some View {
        ScrollView(showsIndicators: false) {
            VStack(spacing: 0) {
                ParallaxHeader(
                    coordinateSpace: CoordinateSpaces.scrollView,
                    defaultHeight: imageHeight
                ) {
                    if let recipeImage = viewModel.recipeImage {
                        Image(uiImage: recipeImage)
                            .resizable()
                            .scaledToFill()
                            .frame(maxHeight: imageHeight + 200)
                            .clipped()
                    } else {
                        Rectangle()
                            .frame(height: 400)
                            .foregroundStyle(
                                LinearGradient(
                                    gradient: Gradient(colors: [.ncGradientDark, .ncGradientLight]),
                                    startPoint: .topLeading,
                                    endPoint: .bottomTrailing
                                )
                            )
                    }
                }
                
                VStack(alignment: .leading) {
                    if viewModel.editMode {
                        RecipeImportSection(viewModel: viewModel, importRecipe: importRecipe)
                    }
                    
                    if viewModel.editMode {
                        RecipeMetadataSection(viewModel: viewModel)
                    }
                    
                    HStack {
                        EditableText(text: $viewModel.observableRecipeDetail.name, editMode: $viewModel.editMode, titleKey: "Recipe Name")
                            .font(.title)
                            .bold()
                        
                        Spacer()
                        
                        if let isDownloaded = viewModel.isDownloaded {
                            Image(systemName: isDownloaded ? "checkmark.circle" : "icloud.and.arrow.down")
                                .foregroundColor(.secondary)
                        }
                    }.padding([.top, .horizontal])
                    
                    if viewModel.observableRecipeDetail.description != "" || viewModel.editMode {
                        EditableText(text: $viewModel.observableRecipeDetail.description, editMode: $viewModel.editMode, titleKey: "Description", lineLimit: 0...5, axis: .vertical)
                            .fontWeight(.medium)
                            .padding(.horizontal)
                            .padding(.top, 2)
                    }
                    
                    // Recipe Body Section
                    RecipeDurationSection(viewModel: viewModel)

                    Divider()
                    
                    LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
                        if(!viewModel.observableRecipeDetail.recipeIngredient.isEmpty || viewModel.editMode) {
                            RecipeIngredientSection(viewModel: viewModel)
                        }
                        if(!viewModel.observableRecipeDetail.recipeInstructions.isEmpty || viewModel.editMode) {
                            RecipeInstructionSection(viewModel: viewModel)
                        }
                        if(!viewModel.observableRecipeDetail.tool.isEmpty || viewModel.editMode) {
                            RecipeToolSection(viewModel: viewModel)
                        }
                        RecipeNutritionSection(viewModel: viewModel)
                    }
                    
                    if !viewModel.editMode {
                        Divider()
                        LazyVGrid(columns: [GridItem(.adaptive(minimum: 400), alignment: .top)]) {
                            RecipeKeywordSection(viewModel: viewModel)
                            MoreInformationSection(viewModel: viewModel)
                        }
                    }
                }
                .padding(.horizontal, 5)
                .background(Rectangle().foregroundStyle(.background).shadow(radius: 5).mask(Rectangle().padding(.top, -20)))
            }
        }
        .coordinateSpace(name: CoordinateSpaces.scrollView)
        .ignoresSafeArea(.container, edges: .top)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar(.visible, for: .navigationBar)
        //.toolbarTitleDisplayMode(.inline)
        .navigationTitle(viewModel.showTitle ? viewModel.recipe.name : "")
            
        .toolbar {
            RecipeViewToolBar(viewModel: viewModel)
        }
        .sheet(isPresented: $viewModel.presentShareSheet) {
            ShareView(recipeDetail: viewModel.observableRecipeDetail.toRecipeDetail(),
                      recipeImage: viewModel.recipeImage,
                      presentShareSheet: $viewModel.presentShareSheet)
        }
        .sheet(isPresented: $viewModel.presentInstructionEditView) {
            EditableListView(
                isPresented: $viewModel.presentInstructionEditView,
                items: $viewModel.observableRecipeDetail.recipeInstructions,
                title: "Instructions",
                emptyListText: "Add cooking steps for fellow chefs to follow.",
                titleKey: "Instruction",
                lineLimit: 0...10,
                axis: .vertical)
        }
        .sheet(isPresented: $viewModel.presentIngredientEditView) {
            EditableListView(
                isPresented: $viewModel.presentIngredientEditView,
                items: $viewModel.observableRecipeDetail.recipeIngredient,
                title: "Ingredients",
                emptyListText: "Start by adding your first ingredient! 🥬",
                titleKey: "Ingredient",
                lineLimit: 0...1,
                axis: .horizontal)
        }
        .sheet(isPresented: $viewModel.presentToolEditView) {
            EditableListView(
                isPresented: $viewModel.presentToolEditView,
                items: $viewModel.observableRecipeDetail.tool,
                title: "Tools",
                emptyListText: "List your tools here. 🍴",
                titleKey: "Tool",
                lineLimit: 0...1,
                axis: .horizontal)
        }
        
        .task {
            // Load recipe detail
            if !viewModel.newRecipe {
                // For existing recipes, load the recipeDetail and image
                let recipeDetail = await appState.getRecipe(
                    id: viewModel.recipe.recipe_id,
                    fetchMode: UserSettings.shared.storeRecipes ? .preferLocal : .onlyServer
                ) ?? RecipeDetail.error
                viewModel.setupView(recipeDetail: recipeDetail)

                // Track as recently viewed
                appState.addToRecentRecipes(viewModel.recipe)

                // Show download badge
                if viewModel.recipe.storedLocally == nil {
                    viewModel.recipe.storedLocally = appState.recipeDetailExists(recipeId: viewModel.recipe.recipe_id)
                }
                viewModel.isDownloaded = viewModel.recipe.storedLocally
                
                // Load recipe image
                viewModel.recipeImage = await appState.getImage(
                    id: viewModel.recipe.recipe_id,
                    size: .FULL,
                    fetchMode: UserSettings.shared.storeImages ? .preferLocal : .onlyServer
                )
                
            } else {
                // Prepare view for a new recipe
                viewModel.setupView(recipeDetail: RecipeDetail())
                viewModel.editMode = true
                viewModel.isDownloaded = false
            }
        }
        .alert(viewModel.alertType.localizedTitle, isPresented: $viewModel.presentAlert) {
            ForEach(viewModel.alertType.alertButtons) { buttonType in
                if buttonType == .OK {
                    Button(AlertButton.OK.rawValue, role: .cancel) {
                        Task {
                            await viewModel.alertAction()
                        }
                    }
                } else if buttonType == .CANCEL {
                    Button(AlertButton.CANCEL.rawValue, role: .cancel) { }
                } else if buttonType == .DELETE {
                    Button(AlertButton.DELETE.rawValue, role: .destructive) {
                        Task {
                            await viewModel.alertAction()
                        }
                    }
                }
            }
        } message: {
            Text(viewModel.alertType.localizedDescription)
        }
        .onAppear {
            if UserSettings.shared.keepScreenAwake {
                UIApplication.shared.isIdleTimerDisabled = true
            }
        }
        .onDisappear {
            UIApplication.shared.isIdleTimerDisabled = false
        }
        .onChange(of: viewModel.editMode) { newValue in
            if newValue && appState.allKeywords.isEmpty {
                Task {
                    appState.allKeywords = await appState.getKeywords(fetchMode: .preferServer).sorted(by: { a, b in
                        a.recipe_count > b.recipe_count
                    })
                }
            }
        }
    }
    
    
    // MARK: - RecipeView ViewModel
    
    class ViewModel: ObservableObject {
        @Published var observableRecipeDetail: ObservableRecipeDetail = ObservableRecipeDetail()
        @Published var recipeDetail: RecipeDetail = RecipeDetail.error
        @Published var recipeImage: UIImage? = nil
        @Published var editMode: Bool = false
        @Published var showTitle: Bool = false
        @Published var isDownloaded: Bool? = nil
        @Published var importUrl: String = ""
        
        @Published var presentShareSheet: Bool = false
        @Published var presentInstructionEditView: Bool = false
        @Published var presentIngredientEditView: Bool = false
        @Published var presentToolEditView: Bool = false
        
        var recipe: Recipe
        var sharedURL: URL? = nil
        var newRecipe: Bool = false
        
        // Alerts
        @Published var presentAlert = false
        var alertType: UserAlert = RecipeAlert.GENERIC
        var alertAction: () async -> () = { }
        
        // Initializers
        init(recipe: Recipe) {
            self.recipe = recipe
        }
        
        init() {
            self.newRecipe = true
            self.recipe = Recipe(
                name: String(localized: "New Recipe"),
                keywords: "",
                dateCreated: "",
                dateModified: "",
                imageUrl: "",
                imagePlaceholderUrl: "",
                recipe_id: 0)
        }
        
        // View setup
        func setupView(recipeDetail: RecipeDetail) {
            self.recipeDetail = recipeDetail
            self.observableRecipeDetail = ObservableRecipeDetail(recipeDetail)
        }
        
        func presentAlert(_ type: UserAlert, action: @escaping () async -> () = {}) {
            alertType = type
            alertAction = action
            presentAlert = true
        }
    }
}



extension RecipeView {
    func importRecipe(from url: String) async -> UserAlert? {
        let (scrapedRecipe, error) = await appState.importRecipe(url: url)
        if let scrapedRecipe = scrapedRecipe {
            viewModel.setupView(recipeDetail: scrapedRecipe)
            // Fetch the image from the server if the import created a recipe with a valid id
            if let recipeId = Int(scrapedRecipe.id), recipeId > 0 {
                viewModel.recipeImage = await appState.getImage(
                    id: recipeId,
                    size: .FULL,
                    fetchMode: .onlyServer
                )
            }
            return nil
        }
        return error
    }
}


// MARK: - Tool Bar


struct RecipeViewToolBar: ToolbarContent {
    @EnvironmentObject var appState: AppState
    @Environment(\.dismiss) private var dismiss
    @ObservedObject var viewModel: RecipeView.ViewModel
    

    var body: some ToolbarContent {
        if viewModel.editMode {
            ToolbarItemGroup(placement: .topBarLeading) {
                Button("Cancel") {
                    viewModel.editMode = false
                    if viewModel.newRecipe {
                        dismiss()
                    }
                }
                
                if !viewModel.newRecipe {
                    Menu {
                        Button(role: .destructive) {
                            viewModel.presentAlert(
                                RecipeAlert.CONFIRM_DELETE,
                                action: {
                                    await handleDelete()
                                }
                            )
                        } label: {
                            Label("Delete Recipe", systemImage: "trash")
                        }
                    } label: {
                        Image(systemName: "ellipsis.circle")
                    }
                }
            }
            
            ToolbarItem(placement: .topBarTrailing) {
                Button {
                    Task {
                        await handleUpload()
                    }
                } label: {
                    if viewModel.newRecipe {
                        Text("Upload Recipe")
                    } else {
                        Text("Upload Changes")
                    }
                }
            }
        } else {
            ToolbarItem(placement: .topBarTrailing) {
                Menu {
                    Button {
                        viewModel.editMode = true
                    } label: {
                        Label("Edit", systemImage: "pencil")
                    }

                    Button {
                        Logger.view.debug("Sharing recipe ...")
                        viewModel.presentShareSheet = true
                    } label: {
                        Label("Share Recipe", systemImage: "square.and.arrow.up")
                    }
                } label: {
                    Image(systemName: "ellipsis.circle")
                }
                
            }
        }
    }
    
    func handleUpload() async {
        if viewModel.newRecipe {
            // Check if the recipe was already created on the server by import
            let importedId = Int(viewModel.observableRecipeDetail.id) ?? 0
            let alreadyCreatedByImport = importedId > 0

            if alreadyCreatedByImport {
                Logger.view.debug("Uploading changes to imported recipe (id: \(importedId)).")
                let (_, alert) = await appState.uploadRecipe(recipeDetail: viewModel.observableRecipeDetail.toRecipeDetail(), createNew: false)
                if let alert {
                    viewModel.presentAlert(alert)
                    return
                }
            } else {
                Logger.view.debug("Uploading new recipe.")
                if let recipeValidationError = recipeValid() {
                    viewModel.presentAlert(recipeValidationError)
                    return
                }

                let (newId, alert) = await appState.uploadRecipe(recipeDetail: viewModel.observableRecipeDetail.toRecipeDetail(), createNew: true)
                if let alert {
                    viewModel.presentAlert(alert)
                    return
                }
                if let newId {
                    viewModel.observableRecipeDetail.id = String(newId)
                }
            }
        } else {
            Logger.view.debug("Uploading changed recipe.")

            guard let _ = Int(viewModel.observableRecipeDetail.id) else {
                viewModel.presentAlert(RequestAlert.REQUEST_DROPPED)
                return
            }

            let (_, alert) = await appState.uploadRecipe(recipeDetail: viewModel.observableRecipeDetail.toRecipeDetail(), createNew: false)
            if let alert {
                viewModel.presentAlert(alert)
                return
            }
        }
        await appState.getCategories()
        let newCategory = viewModel.observableRecipeDetail.recipeCategory.isEmpty ? "*" : viewModel.observableRecipeDetail.recipeCategory
        let oldCategory = viewModel.recipeDetail.recipeCategory.isEmpty ? "*" : viewModel.recipeDetail.recipeCategory
        await appState.getCategory(named: newCategory, fetchMode: .preferServer)
        if oldCategory != newCategory {
            await appState.getCategory(named: oldCategory, fetchMode: .preferServer)
        }
        if let id = Int(viewModel.observableRecipeDetail.id) {
            let _ = await appState.getRecipe(id: id, fetchMode: .onlyServer, save: true)
            // Fetch the image after upload so it displays in view mode
            viewModel.recipeImage = await appState.getImage(id: id, size: .FULL, fetchMode: .onlyServer)
            // Update recipe reference so the view tracks the server-assigned id
            viewModel.recipe = Recipe(
                name: viewModel.observableRecipeDetail.name,
                keywords: viewModel.observableRecipeDetail.keywords.joined(separator: ","),
                dateCreated: "",
                dateModified: "",
                imageUrl: viewModel.observableRecipeDetail.imageUrl,
                imagePlaceholderUrl: "",
                recipe_id: id
            )
        }
        viewModel.newRecipe = false
        viewModel.editMode = false
        viewModel.presentAlert(RecipeAlert.UPLOAD_SUCCESS)
    }
    
    func handleDelete() async {
        let category = viewModel.observableRecipeDetail.recipeCategory
        guard let id = Int(viewModel.observableRecipeDetail.id) else {
            viewModel.presentAlert(RequestAlert.REQUEST_DROPPED)
            return
        }
        if let alert = await appState.deleteRecipe(withId: id, categoryName: viewModel.observableRecipeDetail.recipeCategory) {
            viewModel.presentAlert(alert)
            return
        }
        await appState.getCategories()
        await appState.getCategory(named: category, fetchMode: .preferServer)
        viewModel.presentAlert(RecipeAlert.DELETE_SUCCESS)
        dismiss()
    }
    
    func recipeValid() -> RecipeAlert? {
        // Check if the recipe has a name
        if viewModel.observableRecipeDetail.name.replacingOccurrences(of: " ", with: "") == "" {
            return RecipeAlert.NO_TITLE
        }
        
        // Check if the recipe has a unique name
        for recipeList in appState.recipes.values {
            for r in recipeList {
                if r.name
                    .replacingOccurrences(of: " ", with: "")
                    .lowercased() ==
                    viewModel.observableRecipeDetail.name
                    .replacingOccurrences(of: " ", with: "")
                    .lowercased()
                {
                    return RecipeAlert.DUPLICATE
                }
            }
        }
        return nil
    }
}


