//
// ExerciseListView.swift
// Workouts
//
// Created by rzen on 7/18/25 at 8:38 AM.
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//

import IndieSync
import SwiftUI
import SwiftData

struct ExerciseListView: View {
    @Environment(SyncEngine.self) private var sync
    @Environment(\.dismiss) private var dismiss

    // Resolve the split by id, not a captured entity: editing a seed's exercise from
    // here clones the seed (new identity, old entity deleted), which would dangle a
    // stored `Split`. `currentSplitID` follows that swap.
    @State private var splitID: String
    @Query private var splits: [Split]

    @State private var showingAddSheet: Bool = false
    @State private var itemToEdit: Exercise? = nil
    @State private var itemToDelete: Exercise? = nil
    /// ID of the just-created workout; drives programmatic navigation once the
    /// cache observer delivers the entity a beat after the file write (see
    /// `navigatesToStartedWorkout`).
    @State private var pendingWorkoutID: String? = nil

    @Query(sort: \Workout.start, order: .reverse)
    private var workouts: [Workout]

    @State private var showingActivePrompt = false
    @AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb

    init(split: Split) {
        _splitID = State(initialValue: split.id)
    }

    private var split: Split? {
        let id = sync.currentSplitID(for: splitID)
        return splits.first { $0.id == id }
    }

    private var activeWorkouts: [Workout] {
        workouts.filter { $0.status == .inProgress || $0.status == .notStarted }
    }

    var body: some View {
        Group {
            if let split {
                content(for: split)
            } else {
                ContentUnavailableView("Split Unavailable", systemImage: "dumbbell")
                    .task { dismiss() }
            }
        }
        // Navigate into the workout's log screen once the entity appears in the cache.
        .navigatesToStartedWorkout(pendingWorkoutID: $pendingWorkoutID)
    }

    @ViewBuilder
    private func content(for split: Split) -> some View {
        Form {
            let sortedExercises = split.exercisesArray

            if !sortedExercises.isEmpty {
                ForEach(sortedExercises) { item in
                    ListItem(
                        title: item.name,
                        subtitle: "\(item.sets) × \(item.reps) × \(weightUnit.format(item.weight))"
                    )
                    .swipeActions {
                        Button {
                            itemToDelete = item
                        } label: {
                            Label("Delete", systemImage: "trash")
                        }
                        .tint(.red)
                        Button {
                            itemToEdit = item
                        } label: {
                            Label("Edit", systemImage: "pencil")
                        }
                        .tint(.indigo)
                    }
                }
                .onMove(perform: moveExercises)

                Button {
                    showingAddSheet = true
                } label: {
                    ListItem(title: "Add Exercise")
                }
            } else {
                Text("No exercises added yet.")
                Button(action: { showingAddSheet.toggle() }) {
                    ListItem(title: "Add Exercise")
                }
            }
        }
        .navigationTitle(split.name)
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button("Start This Split") {
                    confirmAndStart()
                }
                .disabled(split.exercisesArray.isEmpty)
            }
        }
        .sheet(isPresented: $showingAddSheet) {
            ExercisePickerView(onExerciseSelected: { exerciseNames in
                addExercises(names: exerciseNames)
            }, allowMultiSelect: true)
        }
        .sheet(item: $itemToEdit) { item in
            ExerciseAddEditView(exercise: item, split: split)
        }
        .confirmationDialog(
            "Delete Exercise?",
            isPresented: Binding(
                get: { itemToDelete != nil },
                set: { if !$0 { itemToDelete = nil } }
            ),
            titleVisibility: .visible,
            presenting: itemToDelete
        ) { item in
            Button("Delete", role: .destructive) {
                deleteExercise(item)
                itemToDelete = nil
            }
            Button("Cancel", role: .cancel) {
                itemToDelete = nil
            }
        } message: { item in
            Text("Remove \"\(item.name)\" from this split?")
        }
        .confirmationDialog(
            activePromptTitle,
            isPresented: $showingActivePrompt,
            titleVisibility: .visible
        ) {
            Button("End Current & Start New") { endActiveThenStart() }
            Button("Start in Parallel") { start() }
            Button("Cancel", role: .cancel) { showingActivePrompt = false }
        } message: {
            Text(activePromptMessage)
        }
    }

    // MARK: - Helpers

    private var activePromptTitle: String {
        activeWorkouts.count == 1 ? "Workout in Progress" : "\(activeWorkouts.count) Workouts in Progress"
    }

    private var activePromptMessage: String {
        let n = activeWorkouts.count
        let those = n == 1 ? "it" : "them"
        return "You already have \(n == 1 ? "a workout" : "\(n) workouts") going. End \(those) first, or run this one alongside."
    }

    /// Prompt before starting if other workouts are still going; otherwise start straight away.
    private func confirmAndStart() {
        if activeWorkouts.isEmpty {
            start()
        } else {
            showingActivePrompt = true
        }
    }

    /// End every in-flight workout (keeping its progress), then start this split.
    private func endActiveThenStart() {
        let toEnd = activeWorkouts.map { WorkoutDocument(from: $0) }
        showingActivePrompt = false
        Task {
            for var doc in toEnd {
                doc.endKeepingProgress()
                await sync.save(workout: doc)
            }
        }
        start()
    }

    private func moveExercises(from source: IndexSet, to destination: Int) {
        guard let split else { return }
        var exercises = split.exercisesArray
        exercises.move(fromOffsets: source, toOffset: destination)
        var doc = SplitDocument(from: split)
        doc.exercises = exercises.enumerated().map { i, ex in
            var ed = ExerciseDocument(from: ex)
            ed.order = i
            return ed
        }
        doc.updatedAt = Date()
        Task { await sync.save(split: doc) }
    }

    private func start() {
        guard let split else { return }
        let startDate = Date()
        let logs = split.exercisesArray.enumerated().map { i, ex in
            WorkoutLogDocument(planFrom: ExerciseDocument(from: ex), order: i, date: startDate)
        }
        let doc = WorkoutDocument(
            schemaVersion: WorkoutDocument.currentSchemaVersion,
            id: ULID.make(),
            splitID: split.id,
            splitName: split.name,
            start: startDate,
            end: nil,
            status: WorkoutStatus.notStarted.rawValue,
            createdAt: startDate,
            updatedAt: startDate,
            logs: logs
        )
        Task {
            await sync.save(workout: doc)
            pendingWorkoutID = doc.id
        }
    }

    private func addExercises(names: [String]) {
        guard let split else { return }
        var doc = SplitDocument(from: split)
        let existingNames = Set(doc.exercises.map { $0.name })
        let base = doc.exercises.count
        let newDocs = names
            .filter { !existingNames.contains($0) }
            .enumerated()
            .map { i, exName -> ExerciseDocument in
                // Seed from the library's authored `**Defaults:**` when available,
                // falling back to a plain 3×10 weighted guess; weight always starts
                // at 0 (there's no prior lift to seed it from).
                let defaults = ExerciseInfoLibrary.info(for: exName)?.defaults
                return ExerciseDocument(
                    id: ULID.make(), name: exName, order: base + i,
                    sets: defaults?.sets ?? 3, reps: defaults?.reps ?? 10, weight: 0,
                    loadType: (defaults?.loadType ?? .weight).rawValue,
                    durationSeconds: defaults?.durationSeconds ?? 0
                )
            }
        doc.exercises.append(contentsOf: newDocs)
        doc.updatedAt = Date()
        Task { await sync.save(split: doc) }
    }

    private func deleteExercise(_ exercise: Exercise) {
        guard let split else { return }
        var doc = SplitDocument(from: split)
        doc.exercises.removeAll { $0.id == exercise.id }
        for i in doc.exercises.indices {
            doc.exercises[i].order = i
        }
        doc.updatedAt = Date()
        Task { await sync.save(split: doc) }
    }
}
