import IndieSync
import SwiftUI
import SwiftData

/// Add or edit one exercise in a routine. Add mode picks a name from the searchable,
/// grouped bundled catalog (`ExerciseCatalog.sections`) and seeds its authored
/// defaults; edit mode pre-fills from the existing exercise. Every save rebuilds the
/// parent `RoutineDocument` and goes through `syncEngine.save(routine:)`. Uses only
/// macOS-safe controls — Steppers/TextField/menu Pickers, never `.pickerStyle(.wheel)`
/// or `.textInputAutocapitalization`.
struct MacExerciseEditorSheet: View {
    @Environment(SyncEngine.self) private var syncEngine
    @Environment(\.dismiss) private var dismiss

    let routineID: String
    /// nil in add mode. Captured as a value in `init` so save never reads a retained
    /// (possibly since-deleted) `@Model`.
    private let exerciseID: String?

    @Query private var routines: [Routine]

    @State private var name: String
    /// The library exercise this row derives from — carried through a rename so the
    /// figure/info link survives. Mirrors `ExerciseDocument.libraryName`.
    @State private var libraryName: String?
    @State private var loadType: LoadType
    @State private var sets: Int
    @State private var reps: Int
    @State private var weight: Double
    @State private var minutes: Int
    @State private var seconds: Int
    @State private var machineEnabled: Bool
    @State private var machineSettings: [MachineSetting]

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

    var isAdding: Bool { exerciseID == nil }

    init(routineID: String, exercise: Exercise?) {
        self.routineID = routineID
        self.exerciseID = exercise?.id
        _name = State(initialValue: exercise?.name ?? "")
        _libraryName = State(initialValue: exercise.flatMap { ex in
            ex.libraryName ?? (ExerciseMotionLibrary.exerciseNames.contains(ex.name) ? ex.name : nil)
        })
        _loadType = State(initialValue: exercise?.loadTypeEnum ?? .weight)
        _sets = State(initialValue: exercise?.sets ?? 3)
        _reps = State(initialValue: exercise?.reps ?? 10)
        _weight = State(initialValue: exercise?.weight ?? 0)
        _minutes = State(initialValue: exercise?.durationMinutes ?? 0)
        _seconds = State(initialValue: exercise?.durationSeconds ?? 0)
        _machineEnabled = State(initialValue: exercise?.machineSettings != nil)
        _machineSettings = State(initialValue: exercise?.machineSettings ?? [])
    }

    private var routine: Routine? {
        let id = syncEngine.currentRoutineID(for: routineID)
        return routines.first { $0.id == id }
    }

    var body: some View {
        NavigationStack {
            editorForm
        }
        .frame(width: 460, height: 560)
        .sheet(isPresented: $showingCatalogPicker) {
            MacExerciseCatalogPicker { picked in
                pick(picked)
            }
        }
    }

    private var editorForm: some View {
        Form {
            Section("Exercise") {
                if name.isEmpty {
                    Button("Select Exercise…") { showingCatalogPicker = true }
                } else {
                    if let libraryName, libraryName != name {
                        LabeledContent("From", value: libraryName)
                    }
                    TextField("Name", text: $name)
                    Button("Choose a Different Exercise…") { showingCatalogPicker = true }
                        .font(.caption)
                }
            }

            Section("Sets & Reps") {
                Stepper(value: $sets, in: 0...20) { LabeledContent("Sets", value: "\(sets)") }
                Stepper(value: $reps, in: 0...100) { LabeledContent("Reps", value: "\(reps)") }
            }

            Section("Load Type") {
                Picker("Load Type", selection: $loadType) {
                    ForEach(LoadType.allCases, id: \.self) { load in
                        Text(load.name).tag(load)
                    }
                }
                .pickerStyle(.segmented)
                .labelsHidden()
            }

            if loadType == .weight {
                Section("Weight") {
                    HStack(spacing: 8) {
                        TextField("Weight", value: $weight, format: .number)
                            .frame(width: 70)
                            .multilineTextAlignment(.trailing)
                            .textFieldStyle(.roundedBorder)
                        Text(weightUnit.abbreviation).foregroundStyle(.secondary)
                        Spacer()
                        Stepper("Weight", value: $weight, in: 0...2000, step: 5).labelsHidden()
                    }
                }
            }

            if loadType == .duration {
                Section("Duration") {
                    Stepper(value: $minutes, in: 0...59) { LabeledContent("Minutes", value: "\(minutes)") }
                    Stepper(value: $seconds, in: 0...59) { LabeledContent("Seconds", value: "\(seconds)") }
                }
            }

            Section {
                Toggle("Machine-based", isOn: $machineEnabled.animation())
                if machineEnabled {
                    MacMachineSettingsEditor(settings: $machineSettings)
                }
            } header: {
                Text("Machine")
            } footer: {
                Text("Turn on for machine-based exercises to record comfort settings (seat height, back-rest incline, pin position…).")
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
        .formStyle(.grouped)
        .navigationTitle(isAdding ? "Add Exercise" : (name.isEmpty ? "Edit Exercise" : name))
        .toolbar {
            ToolbarItem(placement: .cancellationAction) {
                Button("Cancel") { dismiss() }
            }
            ToolbarItem(placement: .confirmationAction) {
                Button("Save") { save(); dismiss() }
                    .disabled(name.trimmingCharacters(in: .whitespaces).isEmpty)
            }
        }
    }

    /// Apply a picked library exercise. Add mode seeds the library's authored defaults
    /// (sets/reps/load/duration); edit mode only relinks the name (parity with iOS,
    /// which never resets an existing exercise's plan on a re-pick).
    private func pick(_ exerciseName: String) {
        name = exerciseName
        libraryName = exerciseName
        guard isAdding, let defaults = ExerciseInfoLibrary.info(for: exerciseName)?.defaults else { return }
        sets = defaults.sets
        reps = defaults.reps
        loadType = defaults.loadType
        minutes = defaults.durationSeconds / 60
        seconds = defaults.durationSeconds % 60
    }

    private func save() {
        guard let routine else { return }

        let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
        let finalName = trimmed.isEmpty ? (libraryName ?? "") : trimmed
        guard !finalName.isEmpty else { return }
        // nil when the name matches the library exercise (unrenamed) — keeps an
        // unrenamed exercise byte-identical on disk.
        let storedLibraryName = (libraryName == finalName) ? nil : libraryName
        let durationSecs = minutes * 60 + seconds

        var doc = RoutineDocument(from: routine)
        if let exerciseID, let idx = doc.exercises.firstIndex(where: { $0.id == exerciseID }) {
            doc.exercises[idx].name = finalName
            doc.exercises[idx].libraryName = storedLibraryName
            doc.exercises[idx].sets = sets
            doc.exercises[idx].reps = reps
            doc.exercises[idx].weight = weight
            doc.exercises[idx].loadType = loadType.rawValue
            doc.exercises[idx].durationSeconds = durationSecs
            doc.exercises[idx].machineSettings = machineEnabled ? machineSettings : nil
        } else {
            let ed = ExerciseDocument(
                id: ULID.make(),
                name: finalName,
                order: doc.exercises.count,
                sets: sets,
                reps: reps,
                weight: weight,
                loadType: loadType.rawValue,
                durationSeconds: durationSecs,
                machineSettings: machineEnabled ? machineSettings : nil,
                libraryName: storedLibraryName
            )
            doc.exercises.append(ed)
        }
        doc.updatedAt = Date()
        Task { await syncEngine.save(routine: doc) }
    }
}

/// A searchable, catalog-grouped exercise-name picker (`ExerciseCatalog.sections`),
/// single-select. Returns the chosen name to `onSelect` and dismisses.
private struct MacExerciseCatalogPicker: View {
    @Environment(\.dismiss) private var dismiss
    let onSelect: (String) -> Void

    @State private var query = ""

    var body: some View {
        NavigationStack {
            List {
                ForEach(ExerciseCatalog.sections(matching: query), id: \.title) { section in
                    Section(section.title) {
                        ForEach(section.names, id: \.self) { name in
                            Button {
                                onSelect(name)
                                dismiss()
                            } label: {
                                Text(name).frame(maxWidth: .infinity, alignment: .leading)
                            }
                            .buttonStyle(.plain)
                        }
                    }
                }
            }
            .searchable(text: $query, prompt: "Search exercises")
            .navigationTitle("Choose Exercise")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") { dismiss() }
                }
            }
        }
        .frame(width: 440, height: 520)
    }
}

/// macOS-safe editor for an ordered list of machine comfort settings — name/value
/// TextField rows with an explicit remove button and an add row. Mirrors the iOS
/// `MachineSettingsEditor` semantics without `.textInputAutocapitalization` (which
/// doesn't exist on macOS). Reorder is dropped (minor on Mac); add/edit/delete stay.
private struct MacMachineSettingsEditor: View {
    @Binding var settings: [MachineSetting]
    @State private var rows: [Row]

    init(settings: Binding<[MachineSetting]>) {
        _settings = settings
        _rows = State(initialValue: settings.wrappedValue.map(Row.init))
    }

    var body: some View {
        ForEach($rows) { $row in
            HStack {
                TextField("Setting", text: $row.name)
                TextField("Value", text: $row.value)
                    .multilineTextAlignment(.trailing)
                    .foregroundStyle(.secondary)
                    .frame(maxWidth: 120)
                Button {
                    rows.removeAll { $0.id == row.id }
                } label: {
                    Image(systemName: "minus.circle.fill").foregroundStyle(.red)
                }
                .buttonStyle(.plain)
            }
        }

        Button {
            rows.append(Row(MachineSetting(name: "", value: "")))
        } label: {
            Label("Add Setting", systemImage: "plus.circle.fill")
        }
        .onChange(of: rows) { _, newRows in
            settings = newRows.map(\.setting)
        }
    }

    /// Session-only identified wrapper so `ForEach` has a stable identity that
    /// survives text edits.
    fileprivate struct Row: Identifiable, Equatable {
        let id = UUID()
        var name: String
        var value: String

        init(_ setting: MachineSetting) {
            name = setting.name
            value = setting.value
        }

        var setting: MachineSetting { MachineSetting(name: name, value: value) }
    }
}
