import AVFoundation
import Combine
import Foundation

@MainActor
final class ChatViewModel: ObservableObject {
    @Published var draft = ""
    @Published private(set) var isSending = false
    @Published private(set) var isRecording = false
    @Published private(set) var errorMessage: String?

    private let session: AppSession
    private var recorder: AVAudioRecorder?
    private var recordingURL: URL?

    init(session: AppSession) {
        self.session = session
    }

    func sendText() async {
        let messageBody = draft
        guard !messageBody.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            return
        }

        isSending = true
        errorMessage = nil

        do {
            try await session.sendText(messageBody)
            draft = ""
        } catch {
            errorMessage = message(for: error)
        }

        isSending = false
    }

    func sendFile(url: URL, removeAfterSending: Bool = false) async {
        isSending = true
        errorMessage = nil

        do {
            try await session.sendFile(url: url)

            if removeAfterSending {
                try? FileManager.default.removeItem(at: url)
            }
        } catch {
            errorMessage = message(for: error)
        }

        isSending = false
    }

    func startRecording() {
        guard !isRecording, !isSending else {
            return
        }

        do {
            let url = try makeRecordingURL()
            let settings: [String: Any] = [
                AVFormatIDKey: kAudioFormatMPEG4AAC,
                AVSampleRateKey: 44_100,
                AVNumberOfChannelsKey: 1,
                AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
            ]

            let recorder = try AVAudioRecorder(url: url, settings: settings)
            recorder.prepareToRecord()

            guard recorder.record() else {
                throw ChatViewModelError.recordingUnavailable
            }

            self.recorder = recorder
            recordingURL = url
            isRecording = true
            errorMessage = nil
        } catch {
            errorMessage = message(for: error)
        }
    }

    func stopRecordingAndSend() {
        guard isRecording, let recorder, let recordingURL else {
            return
        }

        recorder.stop()
        self.recorder = nil
        self.recordingURL = nil
        isRecording = false

        Task {
            await sendFile(url: recordingURL, removeAfterSending: true)
        }
    }

    private func makeRecordingURL() throws -> URL {
        let cachesDirectory = try FileManager.default.url(
            for: .cachesDirectory,
            in: .userDomainMask,
            appropriateFor: nil,
            create: true
        )
        let recordingsDirectory = cachesDirectory.appendingPathComponent(
            "FchatiRecordings",
            isDirectory: true
        )

        try FileManager.default.createDirectory(
            at: recordingsDirectory,
            withIntermediateDirectories: true
        )

        return recordingsDirectory
            .appendingPathComponent(UUID().uuidString)
            .appendingPathExtension("m4a")
    }

    private func message(for error: Error) -> String {
        if let error = error as? LocalizedError, let description = error.errorDescription {
            return description
        }

        return "Unable to send the message."
    }
}

enum ChatViewModelError: LocalizedError {
    case recordingUnavailable

    var errorDescription: String? {
        "Unable to start recording."
    }
}
