import AVFoundation
import OSLog
import Speech

/// The transcribing service that transcribes recorded audio into text on device.
///
/// The service runs the given recorded audio file through a `SpeechAnalyzer` with a `SpeechTranscriber` module, joining the finalized
/// results into the returned transcription. The transcription happens in the locale given at each call — or rather in the closest equivalent
/// the transcriber supports. Any speech model assets still missing for that locale are installed first through ``AssetPreinstalling``;
/// every transcription after that happens entirely offline.
public struct AudioTranscribing: Transcribing {

    // MARK: Properties

    /// The preinstalling service that installs any speech model assets still missing when a transcription starts.
    private let preinstaller = AssetPreinstalling()

    // MARK: Initializers

    /// Creates an audio transcribing service.
    public init() {}

    // MARK: Methods

    /// Transcribes the given recorded audio file into text, deleting the file when the transcription finishes.
    ///
    /// - Parameters:
    ///   - audio: The location of the recorded audio file to transcribe.
    ///   - locale: The locale of the spoken language to transcribe.
    /// - Returns: The transcription of the recorded audio.
    /// - Throws: ``AudioTranscribingError/localeNotSupported`` when the transcriber supports no equivalent of the given locale,
    /// ``AudioTranscribingError/assetsNotInstalled`` when the speech model assets fail to reserve, download, or install,
    /// or any error thrown while reading the audio file or analyzing its contents.
    public func callAsFunction(
        _ audio: URL,
        locale: Locale
    ) async throws -> Transcription {
        defer {
            try? FileManager.default.removeItem(at: audio)
        }

        guard let locale = await SpeechTranscriber.supportedLocale(
            equivalentTo: locale
        ) else {
            throw AudioTranscribingError.localeNotSupported
        }

        let transcriber = SpeechTranscriber(
            locale: locale,
            preset: .transcription
        )

        do {
            try await preinstaller.install(for: locale)
        } catch {
            logger.error("The speech model assets for the \"\(locale.identifier, privacy: .public)\" locale failed to install: \(String(describing: error), privacy: .public)")

            throw AudioTranscribingError.assetsNotInstalled
        }

        let analyzer = SpeechAnalyzer(modules: [transcriber])

        async let text = transcriber.results
            .reduce(into: "") { text, result in
                text += String(result.text.characters)
            }

        let file = try AVAudioFile(forReading: audio)

        if let lastSampleTime = try await analyzer.analyzeSequence(from: file) {
            try await analyzer.finalizeAndFinish(through: lastSampleTime)
        } else {
            await analyzer.cancelAndFinishNow()
        }

        return try await .init(text: text)
    }

}

// MARK: - Constants

/// The logger that records the failures of the audio transcribing service.
private let logger = Logger(
    subsystem: "Features.Recording",
    category: "AudioTranscribing"
)
