//
// WorkoutLogListView.swift
// Workouts
//
// Created by rzen on 7/13/25 at 6:58 PM.
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//

import SwiftUI
import SwiftData

struct WorkoutLogListView: View {
    @Environment(SyncEngine.self) private var sync
    @Environment(\.modelContext) private var modelContext

    let workout: Workout

    /// Working copy of the workout aggregate. Active-session edits mutate this
    /// local value (not the cache entity), which avoids losing rapid taps to the
    /// file→observer→cache lag, and is the single source of truth while the
    /// screen is open.
    @State private var doc: WorkoutDocument

    @State private var showingAddSheet = false
    @State private var logToDelete: WorkoutLogDocument?
    @State private var addedLog: LogRoute?
    @State private var logToEdit: LogRoute?

    /// Drives a programmatic push keyed by a log id, used for the freshly-added
    /// exercise (→ progress flow) and the Edit swipe (→ detail/edit screen). Rows
    /// navigate with plain destination-based `NavigationLink`s; these item-based
    /// `navigationDestination`s are each bound to their own state, so they don't
    /// double-fire the way a value-based `navigationDestination(for:)` would.
    private struct LogRoute: Identifiable, Hashable { let id: String }

    init(workout: Workout) {
        self.workout = workout
        _doc = State(initialValue: WorkoutDocument(from: workout))
    }

    private var sortedLogs: [WorkoutLogDocument] {
        doc.logs.sorted { $0.order < $1.order }
    }

    /// The split this workout was started from (for adding more exercises).
    private var split: Split? {
        guard let splitID = doc.splitID else { return nil }
        return CacheMapper.fetchSplit(id: splitID, in: modelContext)
    }

    var body: some View {
        Group {
            if sortedLogs.isEmpty {
                ContentUnavailableView {
                    Label("No Exercises", systemImage: "figure.strengthtraining.traditional")
                } description: {
                    Text("Add exercises to start your workout.")
                } actions: {
                    Button {
                        showingAddSheet = true
                    } label: {
                        Text("Add Exercise")
                    }
                    .buttonStyle(.borderedProminent)
                }
            } else {
                Form {
                    Section(header: Text(label)) {
                        ForEach(sortedLogs) { log in
                            NavigationLink {
                                ExerciseProgressView(doc: $doc, logID: log.id, onChange: { save() })
                            } label: {
                                CheckboxListItem(
                                    status: workoutStatus(log).checkboxStatus,
                                    title: log.exerciseName,
                                    subtitle: subtitleForLog(log)
                                ) {
                                    cycleStatus(for: log)
                                }
                            }
                            .swipeActions(edge: .leading, allowsFullSwipe: true) {
                                Button {
                                    completeLog(log)
                                } label: {
                                    Label("Complete", systemImage: "checkmark.circle.fill")
                                }
                                .tint(.green)
                            }
                            .swipeActions(edge: .trailing, allowsFullSwipe: false) {
                                Button {
                                    logToDelete = log
                                } label: {
                                    Label("Delete", systemImage: "trash")
                                }
                                .tint(.red)

                                Button {
                                    logToEdit = LogRoute(id: log.id)
                                } label: {
                                    Label("Edit", systemImage: "pencil")
                                }
                                .tint(.blue)
                            }
                        }
                        .onMove(perform: moveLog)
                    }
                }
            }
        }
        .navigationDestination(item: $addedLog) { route in
            // A freshly-added exercise drops straight into its progress flow. The new
            // log already lives in our working doc, so the binding has it before the
            // cache catches up.
            ExerciseProgressView(doc: $doc, logID: route.id, onChange: { save() })
        }
        .navigationDestination(item: $logToEdit) { route in
            // The Edit swipe opens the detail/edit screen, seeded with our working
            // doc so it shows the latest local state immediately.
            ExerciseView(workout: workout, logID: route.id, seedDoc: doc)
        }
        .navigationTitle(doc.splitName ?? Split.unnamed)
        // Absorb edits made in pushed children (ExerciseView/Plan/Notes) once the
        // cache reflects them, so the list shows live status on return.
        .onChange(of: workout.updatedAt) { _, _ in
            doc = WorkoutDocument(from: workout)
        }
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {
                    showingAddSheet.toggle()
                } label: {
                    Image(systemName: "plus")
                }
            }
        }
        .sheet(isPresented: $showingAddSheet) {
            SplitExercisePickerSheet(
                split: split,
                existingExerciseNames: Set(sortedLogs.map { $0.exerciseName })
            ) { exercise in
                addExerciseFromSplit(exercise)
            }
        }
        .confirmationDialog(
            "Delete Exercise?",
            isPresented: Binding(
                get: { logToDelete != nil },
                set: { if !$0 { logToDelete = nil } }
            ),
            titleVisibility: .visible,
            presenting: logToDelete
        ) { log in
            Button("Delete", role: .destructive) {
                deleteLog(log)
                logToDelete = nil
            }
            Button("Cancel", role: .cancel) {
                logToDelete = nil
            }
        }
    }

    // MARK: - Derived

    private var label: String {
        if doc.status == WorkoutStatus.completed.rawValue, let end = doc.end {
            if doc.start.isSameDay(as: end) {
                return "\(doc.start.formattedDate())—\(end.formattedTime())"
            } else {
                return "\(doc.start.formattedDate())—\(end.formattedDate())"
            }
        } else {
            return doc.start.formattedDate()
        }
    }

    private func workoutStatus(_ log: WorkoutLogDocument) -> WorkoutStatus {
        WorkoutStatus(rawValue: log.status) ?? .notStarted
    }

    // MARK: - Mutations (drive the local doc, persist via SyncEngine)

    private func cycleStatus(for log: WorkoutLogDocument) {
        guard let i = doc.logs.firstIndex(where: { $0.id == log.id }) else { return }
        let next: WorkoutStatus
        switch workoutStatus(log) {
        case .notStarted: next = .inProgress
        case .inProgress: next = .completed
        case .completed: next = .notStarted
        case .skipped: next = .notStarted
        }
        doc.logs[i].status = next.rawValue
        doc.logs[i].completed = (next == .completed)
        save()
    }

    private func completeLog(_ log: WorkoutLogDocument) {
        guard let i = doc.logs.firstIndex(where: { $0.id == log.id }) else { return }
        doc.logs[i].status = WorkoutStatus.completed.rawValue
        doc.logs[i].completed = true
        save()
    }

    private func deleteLog(_ log: WorkoutLogDocument) {
        withAnimation {
            doc.logs.removeAll { $0.id == log.id }
            save()
        }
    }

    private func moveLog(from source: IndexSet, to destination: Int) {
        var logs = sortedLogs
        logs.move(fromOffsets: source, toOffset: destination)
        for (index, log) in logs.enumerated() {
            if let i = doc.logs.firstIndex(where: { $0.id == log.id }) {
                doc.logs[i].order = index
            }
        }
        save()
    }

    private func addExerciseFromSplit(_ exercise: Exercise) {
        let now = Date()

        // Reuse the workout's start time only when it's the very first exercise.
        if doc.logs.isEmpty {
            doc.start = now
        }
        doc.end = nil

        let newLog = WorkoutLogDocument(
            id: ULID.make(),
            exerciseName: exercise.name,
            order: doc.logs.count,
            sets: exercise.sets,
            reps: exercise.reps,
            weight: exercise.weight,
            loadType: exercise.loadType,
            durationSeconds: exercise.durationTotalSeconds,
            currentStateIndex: 0,
            completed: false,
            status: WorkoutStatus.notStarted.rawValue,
            notes: nil,
            date: now
        )
        doc.logs.append(newLog)
        save()

        // Push the new exercise straight away.
        addedLog = LogRoute(id: newLog.id)
    }

    /// Recompute the workout's status/end from its logs, then persist.
    private func save() {
        let statuses = doc.logs.map { workoutStatus($0) }
        let allCompleted = !statuses.isEmpty && statuses.allSatisfy { $0 == .completed }
        let anyInProgress = statuses.contains { $0 == .inProgress }
        let allNotStarted = statuses.allSatisfy { $0 == .notStarted }

        if allCompleted {
            doc.status = WorkoutStatus.completed.rawValue
            doc.end = Date()
        } else if anyInProgress || !allNotStarted {
            doc.status = WorkoutStatus.inProgress.rawValue
            doc.end = nil
        } else {
            doc.status = WorkoutStatus.notStarted.rawValue
            doc.end = nil
        }

        doc.updatedAt = Date()
        let snapshot = doc
        Task { await sync.save(workout: snapshot) }
    }

    private func subtitleForLog(_ log: WorkoutLogDocument) -> String {
        if LoadType(rawValue: log.loadType) == .duration {
            let mins = log.durationSeconds / 60
            let secs = log.durationSeconds % 60
            if mins > 0 && secs > 0 {
                return "\(log.sets) × \(mins)m \(secs)s"
            } else if mins > 0 {
                return "\(log.sets) × \(mins) min"
            } else {
                return "\(log.sets) × \(secs) sec"
            }
        } else {
            return "\(log.sets) × \(log.reps) reps × \(log.weight) lbs"
        }
    }
}

// MARK: - Split Exercise Picker Sheet

struct SplitExercisePickerSheet: View {
    @Environment(\.dismiss) private var dismiss

    let split: Split?
    let existingExerciseNames: Set<String>
    let onExerciseSelected: (Exercise) -> Void

    private var availableExercises: [Exercise] {
        guard let split else { return [] }
        return split.exercisesArray.filter { !existingExerciseNames.contains($0.name) }
    }

    var body: some View {
        NavigationStack {
            Group {
                if !availableExercises.isEmpty {
                    List {
                        ForEach(availableExercises) { exercise in
                            Button {
                                onExerciseSelected(exercise)
                                dismiss()
                            } label: {
                                HStack {
                                    VStack(alignment: .leading) {
                                        Text(exercise.name)
                                            .foregroundColor(.primary)
                                        Text("\(exercise.sets) × \(exercise.reps) × \(exercise.weight) lbs")
                                            .font(.caption)
                                            .foregroundColor(.secondary)
                                    }
                                    Spacer()
                                    Image(systemName: "plus.circle")
                                        .foregroundColor(.accentColor)
                                }
                            }
                        }
                    }
                } else if split == nil {
                    ContentUnavailableView(
                        "No Split Selected",
                        systemImage: "dumbbell",
                        description: Text("This workout has no associated split.")
                    )
                } else if split?.exercisesArray.isEmpty == true {
                    ContentUnavailableView(
                        "No Exercises in Split",
                        systemImage: "dumbbell",
                        description: Text("Add exercises to your split first.")
                    )
                } else {
                    ContentUnavailableView(
                        "All Exercises Added",
                        systemImage: "checkmark.circle",
                        description: Text("You've added all exercises from this split.")
                    )
                }
            }
            .navigationTitle("Add Exercise")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") {
                        dismiss()
                    }
                }
            }
        }
    }
}
