import SwiftUI

#if canImport(UIKit)
    import UIKit
#endif

/// A view that drives a recording session.
///
/// The view shows a main button that starts, pauses, and resumes a recording, with a label above it displaying the elapsed recording time.
/// While paused, a send button lets the user submit the recording for processing, showing a progress indicator until the processing finishes.
///
/// All state and control behavior lives in the view's ``Model``; the view itself only renders it and forwards button presses.
public struct RecordingView: View {

    // MARK: Properties

    /// The action that opens a URL, used to open the app's settings from the error alert.
    @Environment(\.openURL)
    private var openURL
    
    /// The font size of the timer's label, scaled relative to the current dynamic type size.
    @ScaledMetric
    private var fontSize = Constant.Size.timer

    /// The model that owns the recording state and drives the view's controls.
    @State
    private var model: Model

    /// The closure invoked with the transcription of every processed recording.
    private let onTranscription: (Transcription) -> Void

    // MARK: Initializers

    /// Creates a recording view in the not-recording state, attached to the given recording and transcribing services.
    ///
    /// - Parameters:
    ///   - capturer: The service that captures the audio from a microphone.
    ///   - transcriber: The service that transcribes the recorded audio into text.
    ///   - locale: The binding to the locale of the spoken language to transcribe.
    ///   - onTranscription: The closure invoked with the transcription of every processed recording. Defaults to a closure that does nothing.
    public init(
        capturer: any Capturing,
        transcriber: any Transcribing,
        locale: Binding<Locale>,
        onTranscription: @escaping (Transcription) -> Void
    ) {
        self.model = .init(
            capturer: capturer,
            transcribe: transcriber,
            locale: locale
        )
        self.onTranscription = onTranscription
    }

    // MARK: Body

    /// The content of the view: the timer label above the main, discard, and send buttons, with the transcription of every processed
    /// recording forwarded to the closure given at initialization, and an alert surfacing any error of the recording flow — offering to
    /// open the app's settings when the microphone permission was denied.
    public var body: some View {
        VStack(
            spacing: Constant.Spacing.stack
        ) {
            if model.shouldShowTimer {
                Text(model.textTimer)
                    .font(.system(size: fontSize))
                    .fontWeight(.bold)
                    .foregroundStyle(.primary)
                    .monospacedDigit()
                    .contentTransition(.numericText())
                    .animation(
                        .easeInOut,
                        value: model.textTimer
                    )
                    .accessibilityLabel(.viewRecordingTimerLabel)
                    .accessibilityValue(model.textTimerAccessible)
            }

            GlassEffectContainer {
                HStack(
                    spacing: Constant.Spacing.stack
                ) {
                    Button {
                        model.pressedMain()
                    } label: {
                        Image(model.iconMain)
                            .resizable()
                    }
                    .buttonStyle(.recording(
                        invertStyle: true
                    ))
                    .disabled(model.shouldDisableMain)
                    .accessibilityLabel(model.labelMain)

                    if model.shouldShowActions {
                        Button {
                            model.pressedSend()
                        } label: {
                            if let icon = model.iconSend {
                                Image(icon)
                                    .resizable()
                            } else {
                                ProgressView()
                            }
                        }
                        .buttonStyle(.recording(
                            invertStyle: !model.isProcessing
                        ))
                        .disabled(model.isProcessing)
                        .accessibilityLabel(model.labelSend)

                        Button {
                            model.pressedDiscard()
                        } label: {
                            Image.Icon.discard
                                .resizable()
                        }
                        .buttonStyle(.recording(
                            invertStyle: true
                        ))
                        .accessibilityLabel(.viewRecordingButtonDiscardLabel)
                    }
                }
            }
        }
        .animation(
            .easeInOut,
            value: model.state
        )
        .onChange(
            of: model.transcription,
            initial: false
        ) { _, newValue in
            if let newValue {
                onTranscription(newValue)
            }
        }
        .alert(
            .viewRecordingAlertErrorTitle,
            isPresented: .init {
                model.error != nil
            } set: { isPresented in
                if !isPresented {
                    model.dismissedError()
                }
            },
            presenting: model.error
        ) { error in
            if error == .permissionDenied, let url = URL.settings {
                Button(.viewRecordingAlertErrorButtonSettings) {
                    openURL(url)
                }
            }

            Button(
                .viewRecordingAlertErrorButtonOk,
                role: .cancel
            ) {
                // Dismissal is handled by the presentation binding.
            }
        } message: { _ in
            if let message = model.textAlertMessage {
                Text(message)
            }
        }
    }

}

// MARK: - Constants

/// The constant values used across the view.
private enum Constant {
    /// The spacing constants.
    enum Size {
        /// The font size of the timer label.
        static let timer: CGFloat = 100
    }
    /// The spacing constants.
    enum Spacing {
        /// The spacing between the elements of a stack.
        static let stack: CGFloat = 16
    }
}

// MARK: - Image+Constants

private extension Image {
    /// The symbol constants.
    enum Icon {
        /// The system symbol of the discard button's image.
        static let discard = Image(systemName: "trash")
    }
}

// MARK: - URL+Constants

private extension URL {
    /// The location that opens the app's settings, where the microphone permission can be granted.
    static let settings: URL? = {
        #if os(macOS)
        .init(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone")
        #else
        .init(string: UIApplication.openSettingsURLString)
        #endif
    }()
}

// MARK: - Previews

#Preview(
    "Recording view"
) {
    RecordingView(
        capturer: DummyCapturing(),
        transcriber: DummyTranscribing(),
        locale: .constant(.current)
    ) { _ in
        // On transcription closure.
    }
}
