import AVFoundation
import CoreMedia

public enum CameraSessionConfigurationError: LocalizedError {
    case addCameraInputFailed
    case addPhotoOutputFailed
    case microphoneUnavailable
    case addMicrophoneInputFailed
    case addMovieOutputFailed

    public var errorDescription: String? {
        switch self {
        case .addCameraInputFailed:
            "Failed to add camera input"
        case .addPhotoOutputFailed:
            "Failed to add photo output"
        case .microphoneUnavailable:
            "Microphone unavailable"
        case .addMicrophoneInputFailed:
            "Failed to add microphone input"
        case .addMovieOutputFailed:
            "Failed to add movie output"
        }
    }
}

public enum CameraSessionConfiguration {
    public static func addCameraInput(session: AVCaptureSession, camera: AVCaptureDevice) throws {
        let input = try AVCaptureDeviceInput(device: camera)
        guard session.canAddInput(input) else {
            throw CameraSessionConfigurationError.addCameraInputFailed
        }
        session.addInput(input)
    }

    public static func addPhotoOutput(session: AVCaptureSession) throws -> AVCapturePhotoOutput {
        let output = AVCapturePhotoOutput()
        guard session.canAddOutput(output) else {
            throw CameraSessionConfigurationError.addPhotoOutputFailed
        }
        session.addOutput(output)
        output.maxPhotoQualityPrioritization = .quality
        return output
    }

    public static func addMovieOutput(
        session: AVCaptureSession,
        includeAudio: Bool,
        durationMs: Int) throws -> AVCaptureMovieFileOutput
    {
        if includeAudio {
            guard let mic = AVCaptureDevice.default(for: .audio) else {
                throw CameraSessionConfigurationError.microphoneUnavailable
            }
            let micInput = try AVCaptureDeviceInput(device: mic)
            guard session.canAddInput(micInput) else {
                throw CameraSessionConfigurationError.addMicrophoneInputFailed
            }
            session.addInput(micInput)
        }

        let output = AVCaptureMovieFileOutput()
        guard session.canAddOutput(output) else {
            throw CameraSessionConfigurationError.addMovieOutputFailed
        }
        session.addOutput(output)
        output.maxRecordedDuration = CMTime(value: Int64(durationMs), timescale: 1000)
        return output
    }
}
