import 'camera_device_type.dart';
import 'camera_position.dart';
import 'camera_capture_format.dart';

/// Represents a camera device discovered by the [availableCameraDevices()] function
class CameraDevice {
  /// The native ID of the camera device instance.
  final String id;

  /// The physical devices this `CameraDevice` contains.
  ///
  /// If this camera device is a **logical camera** (combination of multiple physical cameras), there are multiple cameras in this list.
  /// If this camera device is a **physical camera**, there is only a single element in this list.
  ///
  /// You can check if the camera is a logical multi-camera by using the [isMultiCam] property.
  final List<CameraDeviceType> devices;

  /// Specifies the physical position of this camera. (back or front)
  final CameraPosition position;

  /// A friendly localized name describing the camera.
  final String name;

  /// Specifies whether this camera supports enabling flash for photo capture.
  final bool hasFlash;

  /// Specifies whether this camera supports continuously enabling the flash to act like a torch (flash with video capture)
  final bool hasTorch;

  /// A property indicating whether the device is a virtual multi-camera consisting of multiple combined physical cameras.
  ///
  /// Examples:
  /// The Dual Camera, which supports seamlessly switching between a wide and telephoto camera while zooming and generating depth data from the disparities between the different points of view of the physical cameras.
  /// The TrueDepth Camera, which generates depth data from disparities between a YUV camera and an Infrared camera pointed in the same direction.
  final bool isMultiCam;

  /// Whether this camera device supports using Video Recordings (`video={true}`) and Frame Processors (`frameProcessor={...}`) at the same time. See ["The `supportsParallelVideoProcessing` prop"](https://mrousavy.github.io/react-native-vision-camera/docs/guides/devices#the-supportsparallelvideoprocessing-prop) for more information.
  ///
  /// If this property is `false`, you can only enable `video` or add a `frameProcessor`, but not both.
  /// On iOS this value is always `true`.
  /// On newer Android devices this value is always `true`.
  /// On older Android devices this value is `false` if the Camera's hardware level is `LEGACY` or `LIMITED`, `true` otherwise. (See [`INFO_SUPPORTED_HARDWARE_LEVEL`](https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL) or [the tables at "Regular capture"](https://developer.android.com/reference/android/hardware/camera2/CameraDevice#regular-capture))
  final bool supportsParallelVideoProcessing;

  /// Whether this camera supports taking photos in RAW format
  ///
  /// **! Work in Progress !**
  final bool supportsRawCapture;

  /// Whether this camera supports taking photos with depth data.
  ///
  /// **! Work in Progress !**
  final bool supportsDepthCapture;

  /// Whether this camera device supports low light boost.
  final bool supportsLowLightBoost;

  /// Specifies whether this device supports focusing)
  final bool supportsFocus;

  /// Minimum available zoom factor. (e.g. `1`)
  final double minZoom;

  /// Maximum available zoom factor. (e.g. `128`)
  final double maxZoom;

  /// The zoom factor where the camera is "neutral".
  ///
  /// For single-physical cameras this property is always `1.0`.
  /// For multi cameras this property is a value between `minZoom` and `maxZoom`, where the camera is in _wide-angle_ mode and hasn't switched to the _ultra-wide-angle_ ("fish-eye") or telephoto camera yet.
  ///
  /// Use this value as an initial value for the zoom property if you implement custom zoom. (e.g. reanimated shared value should be initially set to this value)
  /// ```const device = ...
  ///
  /// const zoom = useSharedValue(device.neutralZoom) // <-- initial value so it doesn't start at ultra-wide
  /// const cameraProps = useAnimatedProps(() => ({
  ///   zoom: zoom.value
  /// }))```
  final double neutralZoom;

  ///  All available formats for this camera device. Use this to find the best format for your use case and set it to the [CameraDevice.formats] property.
  final List<CameraCaptureFormat> formats;

  const CameraDevice(
      this.id,
      this.devices,
      this.position,
      this.name,
      this.hasFlash,
      this.hasTorch,
      this.isMultiCam,
      this.supportsParallelVideoProcessing,
      this.supportsRawCapture,
      this.supportsDepthCapture,
      this.supportsLowLightBoost,
      this.supportsFocus,
      this.minZoom,
      this.maxZoom,
      this.neutralZoom,
      this.formats);

  factory CameraDevice.fromMap(Map map) {
    return CameraDevice(
        map['id'],
        (map['devices'] as List<Object?>)
            .where((element) => element != null)
            .map((e) => e as String)
            .map(cameraDeviceTypeFromString)
            .toList(),
        cameraPositionFromString(map['position']),
        map['name'],
        map['hasFlash'],
        map['hasTorch'],
        map['isMultiCam'],
        map['supportsParallelVideoProcessing'],
        map['supportsRawCapture'],
        map['supportsDepthCapture'],
        map['supportsLowLightBoost'],
        map['supportsFocus'],
        map['minZoom'],
        map['maxZoom'],
        map['neutralZoom'],
        (map['formats'] as List<Object?>)
            .where((element) => element != null)
            .map((e) => e!)
            .map((e) => CameraCaptureFormat.fromMap(e as Map))
            .toList());
  }
}
