//
// SettingsView.swift
// Workouts
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//

import SwiftUI
import AVFoundation
import IndieAbout
import IndieBackup

struct SettingsView: View {
    @Environment(SyncEngine.self) private var sync
    @Environment(AppServices.self) private var services

    @AppStorage("restSeconds") private var restSeconds: Int = 45
    @AppStorage("doneCountdownSeconds") private var doneCountdownSeconds: Int = 5
    @AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb
    @AppStorage("figureProfile") private var figureProfile: FigureProfile = .neutral
    @AppStorage("speakExerciseCues") private var speakExerciseCues = false
    @AppStorage("spokenCueStyle") private var spokenCueStyle: SpokenCueStyle = .cues
    @AppStorage(SpeechSettings.voiceIdentifierKey) private var speechVoiceIdentifier = ""

    /// Installed English voices, best quality first. Loaded on appear (not per render) since
    /// `speechVoices()` is a touch heavy and the list only changes when the user downloads a
    /// voice in system settings — re-read on each appear catches a return from doing exactly that.
    @State private var installedVoices: [AVSpeechSynthesisVoice] = []
    @State private var showingSpeechAdjustments = false

    var body: some View {
        NavigationStack {
            Form {
                // MARK: - Workout Section
                Section {
                    SecondsStepperRow(title: "Rest Between Sets", range: 10...180, seconds: $restSeconds)

                    Stepper(value: $doneCountdownSeconds, in: 3...20, step: 1) {
                        HStack {
                            Text("Auto-Finish Countdown")
                            Spacer()
                            Text("\(doneCountdownSeconds)s").foregroundColor(.secondary)
                        }
                    }

                    Picker("Weight Unit", selection: $weightUnit) {
                        ForEach(WeightUnit.allCases, id: \.self) { unit in
                            Text(unit.displayName).tag(unit)
                        }
                    }

                    Picker("Body Type", selection: $figureProfile) {
                        ForEach(FigureProfile.allCases, id: \.self) { profile in
                            Text(profile.displayName).tag(profile)
                        }
                    }
                } header: {
                    Text("Workout")
                } footer: {
                    Text("How long the watch waits on the finish screen before completing an exercise automatically.")
                }

                // MARK: - Voice Section
                voiceSection

                // MARK: - Goals Section
                Section(header: Text("Goals")) {
                    NavigationLink {
                        GoalsListView()
                    } label: {
                        HStack {
                            Label("Goals", systemImage: "target")
                            Spacer()
                            Text("\(GoalKind.allCases.count)")
                                .foregroundStyle(.secondary)
                        }
                    }
                }

                // MARK: - iCloud Sync Section
                Section {
                    switch sync.iCloudStatus {
                    case .checking:
                        Label("Connecting…", systemImage: "arrow.triangle.2.circlepath.icloud")
                            .foregroundStyle(.secondary)
                    case .available:
                        Label("Connected", systemImage: "checkmark.icloud")
                            .foregroundStyle(.secondary)
                    case .unavailable:
                        Label("iCloud unavailable", systemImage: "xmark.icloud")
                            .foregroundStyle(.orange)
                    }
                    if let error = sync.lastSyncError {
                        Label(error, systemImage: "exclamationmark.icloud.fill")
                            .foregroundStyle(.orange)
                    }

                    NavigationLink {
                        DiagnosticsView()
                    } label: {
                        Label("Diagnostics", systemImage: "stethoscope")
                    }
                } header: {
                    Text("iCloud Sync")
                }

                // MARK: - Backups Section
                BackupsSectionView(controller: services.backupController)

                // MARK: - Developer Section (debug builds only)
                #if DEBUG
                Section {
                    NavigationLink {
                        DuplicateCleanupView()
                    } label: {
                        Label("Clean Up Duplicates", systemImage: "wrench.and.screwdriver")
                    }
                    NavigationLink {
                        ExerciseFigureReviewView()
                    } label: {
                        Label("Review Exercise Figures", systemImage: "figure.strengthtraining.traditional")
                    }
                } header: {
                    Text("Developer")
                }
                #endif

                // MARK: - About Section
                Section {
                    IndieAbout(configuration: AppInfoConfiguration(
                        documents: [
                            .license(extension: "md")
                        ],
                        changelogDocument: .changelog()
                    ))
                }
            }
            .navigationTitle("Settings")
            .onChange(of: restSeconds) { _, _ in services.watchBridge.pushAll() }
            .onChange(of: doneCountdownSeconds) { _, _ in services.watchBridge.pushAll() }
            .onChange(of: weightUnit) { _, _ in services.watchBridge.pushAll() }
            .onAppear {
                installedVoices = SpeechSettings.availableVoices()
                // A previously-chosen voice that's since been uninstalled falls back to Automatic
                // so the picker shows a valid selection (the announcer already falls back too).
                if !speechVoiceIdentifier.isEmpty,
                   !installedVoices.contains(where: { $0.identifier == speechVoiceIdentifier }) {
                    speechVoiceIdentifier = ""
                }
            }
            .sheet(isPresented: $showingSpeechAdjustments) {
                SpeechAdjustmentsView()
            }
        }
    }

    // MARK: - Voice

    /// True once at least one enhanced or premium voice is installed — the nudge to go
    /// download a nicer voice only shows when the user is still on a basic (default) voice.
    private var hasUpgradedVoice: Bool {
        installedVoices.contains { $0.quality != .default }
    }

    /// All spoken-instruction controls. The voice and the speed/pitch/volume adjustments also
    /// govern the library exercise Speak button; only the toggle gates the automatic workout cue.
    @ViewBuilder
    private var voiceSection: some View {
        Section {
            if !hasUpgradedVoice {
                premiumVoiceCallout
            }

            Toggle("Speak Exercise Cues", isOn: $speakExerciseCues)

            if speakExerciseCues {
                Picker("Narration", selection: $spokenCueStyle) {
                    ForEach(SpokenCueStyle.allCases) { style in
                        Text(style.displayName).tag(style)
                    }
                }
            }

            Picker("Voice", selection: $speechVoiceIdentifier) {
                Text("Automatic").tag("")
                ForEach(installedVoices, id: \.identifier) { voice in
                    Text(SpeechSettings.displayLabel(name: voice.name, quality: voice.quality))
                        .tag(voice.identifier)
                }
            }

            Button {
                showingSpeechAdjustments = true
            } label: {
                HStack {
                    Label("Speed, Pitch & Volume", systemImage: "slider.horizontal.3")
                    Spacer()
                    Image(systemName: "chevron.right")
                        .font(.footnote.weight(.semibold))
                        .foregroundStyle(.tertiary)
                }
            }
            .tint(.primary)

            Button {
                let announcer = services.speechAnnouncer
                if announcer.isSpeaking {
                    announcer.stop()
                } else {
                    announcer.speak("This is how your exercise cues will sound.")
                }
            } label: {
                Label(services.speechAnnouncer.isSpeaking ? "Stop Preview" : "Preview Voice",
                      systemImage: services.speechAnnouncer.isSpeaking ? "stop.circle" : "play.circle")
            }
        } header: {
            Text("Voice")
        } footer: {
            Text("Speak Exercise Cues narrates a running workout hands-free. Coming Up & Countdown announces the next exercise during a rest and counts “in 1, 2, 3, GO!” before each set; Setup & Form Read reads each exercise's cues aloud when it starts. Tap a library exercise's speaker to hear its full instructions anytime.")
        }
    }

    /// Shown only when no enhanced/premium voice is installed: points the user to the system
    /// voice download, the one lever that actually makes speech sound less robotic.
    private var premiumVoiceCallout: some View {
        Label {
            VStack(alignment: .leading, spacing: 2) {
                Text("Using a basic voice")
                    .font(.subheadline.weight(.semibold))
                Text("For a much more natural sound, download a Premium voice in Settings › Accessibility › Spoken Content › Voices, then choose it above.")
                    .font(.footnote)
                    .foregroundStyle(.secondary)
            }
        } icon: {
            Image(systemName: "sparkles")
                .foregroundStyle(.tint)
        }
    }
}

/// The speed / pitch / volume knobs, split into their own sheet with a reset. These are the
/// fine-tuning controls, kept out of the main Voice list; they write the same `@AppStorage`
/// keys the announcer reads, so changes preview live.
private struct SpeechAdjustmentsView: View {
    @Environment(\.dismiss) private var dismiss
    @Environment(AppServices.self) private var services

    @AppStorage(SpeechSettings.rateKey) private var speechRate = SpeechSettings.rateDefault
    @AppStorage(SpeechSettings.pitchKey) private var speechPitch = SpeechSettings.pitchDefault
    @AppStorage(SpeechSettings.volumeKey) private var speechVolume = SpeechSettings.volumeDefault

    /// True when every knob already sits at its default — disables the Reset button.
    private var isDefault: Bool {
        speechRate == SpeechSettings.rateDefault
            && speechPitch == SpeechSettings.pitchDefault
            && speechVolume == SpeechSettings.volumeDefault
    }

    var body: some View {
        NavigationStack {
            Form {
                Section {
                    voiceSlider("Speed", value: $speechRate, range: SpeechSettings.rateRange,
                                low: "tortoise", high: "hare")
                    voiceSlider("Pitch", value: $speechPitch, range: SpeechSettings.pitchRange,
                                low: "arrow.down", high: "arrow.up")
                    voiceSlider("Volume", value: $speechVolume, range: SpeechSettings.volumeRange,
                                low: "speaker.fill", high: "speaker.wave.3.fill")
                }

                Section {
                    Button {
                        let announcer = services.speechAnnouncer
                        if announcer.isSpeaking {
                            announcer.stop()
                        } else {
                            announcer.speak("This is how your exercise cues will sound.")
                        }
                    } label: {
                        Label(services.speechAnnouncer.isSpeaking ? "Stop Preview" : "Preview",
                              systemImage: services.speechAnnouncer.isSpeaking ? "stop.circle" : "play.circle")
                    }

                    Button(role: .destructive, action: resetToDefaults) {
                        Label("Reset to Defaults", systemImage: "arrow.counterclockwise")
                    }
                    .disabled(isDefault)
                }
            }
            .navigationTitle("Speed, Pitch & Volume")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done") { dismiss() }
                }
            }
            .onDisappear { services.speechAnnouncer.stop() }
        }
        .presentationDetents([.medium, .large])
    }

    private func resetToDefaults() {
        withAnimation {
            speechRate = SpeechSettings.rateDefault
            speechPitch = SpeechSettings.pitchDefault
            speechVolume = SpeechSettings.volumeDefault
        }
    }

    /// A labeled slider row with low/high symbol endcaps.
    private func voiceSlider(_ title: String, value: Binding<Double>,
                             range: ClosedRange<Double>, low: String, high: String) -> some View {
        VStack(alignment: .leading, spacing: 2) {
            Text(title)
                .font(.subheadline)
            Slider(value: value, in: range) {
                Text(title)
            } minimumValueLabel: {
                Image(systemName: low).foregroundStyle(.secondary).imageScale(.small)
            } maximumValueLabel: {
                Image(systemName: high).foregroundStyle(.secondary).imageScale(.small)
            }
        }
    }
}
