import type { Image } from 'react-native-nitro-image'
import type { HybridObject } from 'react-native-nitro-modules'
import type {
  CameraController,
  CameraControllerConfiguration,
} from '../CameraController.nitro'
import type { CameraPosition } from '../common-types/CameraPosition'
import type {
  Constraint,
  FPSConstraint,
  PhotoHDRConstraint,
  PreviewStabilizationModeConstraint,
  ResolutionBiasConstraint,
  VideoDynamicRangeConstraint,
  VideoStabilizationModeConstraint,
} from '../common-types/Constraint'
import type { DeviceType } from '../common-types/DeviceType'
import type { DynamicRange } from '../common-types/DynamicRange'
import type { MeteringMode } from '../common-types/FocusOptions'
import type { MediaType } from '../common-types/MediaType'
import type { OutputStreamType } from '../common-types/OutputStreamType'
import type { PixelFormat } from '../common-types/PixelFormat'
import type { QualityPrioritization } from '../common-types/QualityPrioritization'
import type { Range } from '../common-types/Range'
import type { Size } from '../common-types/Size'
import type {
  StabilizationMode,
  TargetStabilizationMode,
} from '../common-types/StabilizationMode'
import type { WhiteBalanceGains } from '../common-types/WhiteBalanceGains'
import type { Photo } from '../instances/Photo.nitro'
import type {
  CameraDepthFrameOutput,
  DepthFrameOutputOptions,
} from '../outputs/CameraDepthFrameOutput.nitro'
import type { FrameOutputOptions } from '../outputs/CameraFrameOutput.nitro'
import type { CameraOutput } from '../outputs/CameraOutput.nitro'
import type {
  CameraPhotoOutput,
  CapturePhotoCallbacks,
  CapturePhotoSettings,
  PhotoOutputOptions,
} from '../outputs/CameraPhotoOutput.nitro'
import type { CameraPreviewOutput } from '../outputs/CameraPreviewOutput.nitro'
import type {
  CameraVideoOutput,
  VideoOutputOptions,
} from '../outputs/CameraVideoOutput.nitro'
import type { CameraSession } from '../session/CameraSession.nitro'
import type { CameraSessionConfig } from '../session/CameraSessionConfig.nitro'

/**
 * The {@linkcode CameraDevice} represents a physical- or
 * virtual Camera Device.
 *
 * Examples:
 * - Physical: The 0.5x ultra-wide-angle Camera
 * - Virtual: The Triple-Camera consisting of the 0.5x, the 1x, and the 3x Camera
 * - TrueDepth Face ID: A virtual Camera consisting of a color, and a depth Camera
 */
export interface CameraDevice
  extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
  // pragma MARK: Device Metadata
  /**
   * A unique identifier for this camera device.
   */
  readonly id: string
  /**
   * The model identifier for this camera device.
   */
  readonly modelID: string
  /**
   * A user friendly camera name shown by the system.
   */
  readonly localizedName: string
  /**
   * The hardware manufacturer name for this device.
   */
  readonly manufacturer: string
  /**
   * The camera hardware type, for example
   * {@linkcode DeviceType | 'wide-angle'}.
   */
  readonly type: DeviceType
  /**
   * The position of this camera on the device.
   */
  readonly position: CameraPosition
  /**
   * If this {@linkcode CameraDevice} is a virtual
   * device (see {@linkcode isVirtualDevice}), this
   * property contains a list of all constituent
   * physical devices this virtual device is made of.
   *
   * For physical devices, this returns an empty array.
   */
  readonly physicalDevices: CameraDevice[]
  /**
   * Gets whether this {@linkcode CameraDevice}
   * is a virtual device, which consists of two or
   * more physical {@linkcode CameraDevice}s.
   *
   * Devices like the "Triple-Camera" (0.5x + 1x + 3x)
   * or the "FaceID Camera" (color + depth) are virtual
   * devices.
   *
   * To access the individual physical devices
   * a virtual device consists of, use {@linkcode physicalDevices}.
   */
  readonly isVirtualDevice: boolean

  // pragma MARK: Output Capabilities
  /**
   * Get a list of all supported {@linkcode PixelFormat}s this
   * {@linkcode CameraDevice} can stream in.
   */
  readonly supportedPixelFormats: PixelFormat[]

  /**
   * Get a list of all supported resolutions this {@linkcode CameraDevice}
   * can produce for outputs of the given {@linkcode OutputStreamType}.
   *
   * If a given {@linkcode OutputStreamType} can not be streamed to at
   * all, an empty array (`[]`) will be returned.
   *
   * @discussion
   * While a {@linkcode CameraDevice} may be able to stream in the
   * given output resolutions individually, it is not guaranteed that
   * all resolutions are available when streaming to multiple
   * {@linkcode CameraOutput} with multiple {@linkcode Constraint}s
   * enabled.
   *
   * The {@linkcode CameraSession} internally negotiates available
   * output resolutions based on {@linkcode Constraint}s, and potentially
   * downgrades (or upgrades) target resolutions if needed.
   *
   * @see {@linkcode VideoOutputOptions.targetResolution}
   * @see {@linkcode PhotoOutputOptions.targetResolution}
   * @see {@linkcode FrameOutputOptions.targetResolution}
   * @see {@linkcode DepthFrameOutputOptions.targetResolution}
   * @see {@linkcode ResolutionBiasConstraint}
   *
   * @example
   * Get all resolutions for Photo capture:
   * ```ts
   * const device = ...
   * const resolutions = device.getSupportedResolutions('photo')
   * ```
   */
  getSupportedResolutions(outputStreamType: OutputStreamType): Size[]

  /**
   * Returns whether this {@linkcode CameraDevice}
   * supports streaming to the given {@linkcode output}.
   *
   * @discussion
   * Usually, a {@linkcode CameraDevice} can stream to all
   * {@linkcode CameraOutput}s, with very rare exceptions.
   *
   * For example, Depth Frame Outputs ({@linkcode CameraDepthFrameOutput})
   * may not be supported on all {@linkcode CameraDevice}s - see
   * {@linkcode mediaTypes}.
   */
  supportsOutput(output: CameraOutput): boolean

  // pragma MARK: Capabilities
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports capturing Photos in High Dynamic Range (HDR).
   *
   * @discussion
   * Photo HDR captures multiple images in various
   * exposure settings (often one under-exposed, one
   * over-exposed and one perfectly exposed), and fuses
   * the images together into a single image with a wider
   * range of colors, which results in darker blacks and
   * brighter whites.
   *
   * @discussion
   * While a device may support Photo HDR individually,
   * it is not guaranteed to be supported with all
   * possible feature and output combinations.
   *
   * The {@linkcode Constraint} API (specifically {@linkcode PhotoHDRConstraint})
   * internally negotiates the target Photo HDR preference with
   * other enabled features (such as {@linkcode ResolutionBiasConstraint})
   * and all connected {@linkcode CameraOutput}s to find a best-matching
   * supported combination on this device.
   *
   * Alternatively, to find out if a specific combination is supported upfront,
   * use the {@linkcode isSessionConfigSupported | isSessionConfigSupported(...)}
   * method with your desired outputs and constraints applied.
   *
   * @see {@linkcode PhotoHDRConstraint.photoHDR}
   * @see {@linkcode CameraPhotoOutput}
   */
  readonly supportsPhotoHDR: boolean

  /**
   * Get a list of all {@linkcode DynamicRange}s this
   * {@linkcode CameraDevice} supports streaming at,
   * often affecting both Video recordings and Preview.
   *
   * @discussion
   * Unlike Photo HDR, Video Dynamic Ranges are not
   * only extra processing, but often entirely different
   * sensor readouts.
   * While Photo HDR might capture multiple bracketed Photos
   * to fuse them together to expose more colors, Video
   * Dynamic Ranges sometimes stream in an entirely different
   * Pixel Format (e.g. 10-bit instead of 8-bit), and use
   * a different YUV Transfer Matrix to compose pixels from
   * sensor data.
   *
   * @discussion
   * While a device may support a given {@linkcode DynamicRange}
   * individually, it is not guaranteed to be supported with all
   * possible feature and output combinations.
   *
   * The {@linkcode Constraint} API (specifically {@linkcode VideoDynamicRangeConstraint})
   * internally negotiates the target Dynamic Range with
   * other enabled features (such as {@linkcode ResolutionBiasConstraint} or
   * {@linkcode FPSConstraint}) and all connected {@linkcode CameraOutput}s
   * to find a best-matching supported combination on this device.
   *
   * Alternatively, to find out if a specific combination is supported upfront,
   * use the {@linkcode isSessionConfigSupported | isSessionConfigSupported(...)}
   * method with your desired outputs and constraints applied.
   *
   * @see {@linkcode VideoDynamicRangeConstraint.videoDynamicRange}
   * @see {@linkcode CameraVideoOutput}
   */
  readonly supportedVideoDynamicRanges: DynamicRange[]

  /**
   * Gets all available Frame Rate Ranges this
   * {@linkcode CameraDevice} supports individually.
   *
   * Frame Rates are expressed in FPS (Frames per Second),
   * for example `60` refers to 60 FPS (= 16.666ms per Frame).
   *
   * @discussion
   * Use {@linkcode supportsFPS | supportsFPS(...)} as a convenience
   * method to find out if a given fixed frame rate is supported
   * individually.
   *
   * @discussion
   * While a device may support a given FPS {@linkcode Range}
   * individually, it is not guaranteed to be supported with all
   * possible feature and output combinations.
   *
   * The {@linkcode Constraint} API (specifically {@linkcode FPSConstraint})
   * internally negotiates the target {@linkcode FPSConstraint.fps | fps}
   * with other enabled features (such as {@linkcode VideoStabilizationModeConstraint})
   * and all connected {@linkcode CameraOutput}s to find a best-matching
   * supported combination on this device.
   *
   * Alternatively, to find out if a specific combination is supported upfront,
   * use the {@linkcode isSessionConfigSupported | isSessionConfigSupported(...)}
   * method with your desired outputs and constraints applied.
   */
  readonly supportedFPSRanges: Range[]
  /**
   * Gets whether the given {@linkcode fps} is individually supported
   * by any of the {@linkcode supportedFPSRanges} on this {@linkcode CameraDevice}.
   *
   * @discussion
   * While a device may support a given FPS {@linkcode Range}
   * individually, it is not guaranteed to be supported with all
   * possible feature and output combinations.
   *
   * The {@linkcode Constraint} API (specifically {@linkcode FPSConstraint})
   * internally negotiates the target {@linkcode FPSConstraint.fps | fps}
   * with other enabled features (such as {@linkcode VideoStabilizationModeConstraint})
   * and all connected {@linkcode CameraOutput}s to find a best-matching
   * supported combination on this device.
   *
   * Alternatively, to find out if a specific combination is supported upfront,
   * use the {@linkcode isSessionConfigSupported | isSessionConfigSupported(...)}
   * method with your desired outputs and constraints applied.
   *
   * @see {@linkcode supportedFPSRanges}
   */
  supportsFPS(fps: number): boolean

  /**
   * Returns whether this {@linkcode CameraDevice} supports
   * the given {@linkcode StabilizationMode} for video streams
   * (e.g. {@linkcode CameraVideoOutput}).
   *
   * @discussion
   * While a device may support a given {@linkcode StabilizationMode}
   * individually, it is not guaranteed to be supported with all
   * possible feature and output combinations.
   *
   * The {@linkcode Constraint} API (specifically
   * {@linkcode VideoStabilizationModeConstraint}) internally
   * negotiates the target {@linkcode VideoStabilizationModeConstraint.videoStabilizationMode | videoStabilizationMode}
   * with other enabled features (such as {@linkcode FPSConstraint}) and
   * all connected {@linkcode CameraOutput}s to find a best-matching
   * supported combination on this device.
   *
   * Alternatively, to find out if a specific combination is supported upfront,
   * use the {@linkcode isSessionConfigSupported | isSessionConfigSupported(...)}
   * method with your desired outputs and constraints applied.
   */
  supportsVideoStabilizationMode(
    videoStabilizationMode: TargetStabilizationMode,
  ): boolean
  /**
   * Returns whether this {@linkcode CameraDevice} supports
   * the given {@linkcode StabilizationMode} for preview streams
   * (e.g. {@linkcode CameraPreviewOutput}).
   *
   * @discussion
   * While a device may support a given {@linkcode StabilizationMode}
   * individually, it is not guaranteed to be supported with all
   * possible feature and output combinations.
   *
   * The {@linkcode Constraint} API (specifically
   * {@linkcode PreviewStabilizationModeConstraint}) internally
   * negotiates the target {@linkcode PreviewStabilizationModeConstraint.previewStabilizationMode | previewStabilizationMode}
   * with other enabled features (such as {@linkcode FPSConstraint}) and
   * all connected {@linkcode CameraOutput}s to find a best-matching
   * supported combination on this device.
   *
   * Alternatively, to find out if a specific combination is supported upfront,
   * use the {@linkcode isSessionConfigSupported | isSessionConfigSupported(...)}
   * method with your desired outputs and constraints applied.
   */
  supportsPreviewStabilizationMode(
    previewStabilizationMode: TargetStabilizationMode,
  ): boolean
  /**
   * Returns `true` if this {@linkcode CameraDevice} supports
   * delivering preview {@linkcode Image} instances before
   * the actual {@linkcode Photo} is available.
   *
   * On iOS, this is always `true`.
   *
   * @see {@linkcode PhotoOutputOptions.previewImageTargetSize}
   * @see {@linkcode CapturePhotoCallbacks.onPreviewImageAvailable}
   */
  readonly supportsPreviewImage: boolean

  /**
   * Returns `true` if this {@linkcode CameraDevice} supports
   * capturing photos with the {@linkcode QualityPrioritization | 'speed'}
   * {@linkcode QualityPrioritization}.
   *
   * @see {@linkcode PhotoOutputOptions.qualityPrioritization}
   */
  readonly supportsSpeedQualityPrioritization: boolean

  // pragma MARK: Focal Length
  /**
   * Returns the Camera Device's nominal focal length in 35mm film format.
   */
  readonly focalLength?: number
  /**
   * The size (ƒ number) of the lens diaphragm.
   */
  readonly lensAperture: number

  // pragma MARK: Continuity
  /**
   * Returns `true` if this camera is a Continuity Camera.
   * On non iOS platforms this is always `false`.
   *
   * @platform iOS
   */
  readonly isContinuityCamera: boolean
  /**
   * The matching Desk View camera for this Continuity Camera, if available.
   *
   * @platform iOS
   */
  readonly companionDeskViewCamera?: CameraDevice

  // pragma MARK: Methods
  /**
   * Represents all {@linkcode MediaType}s this
   * {@linkcode CameraDevice} can capture.
   *
   * Most Cameras return {@linkcode MediaType | ['video']}.
   * Virtual Cameras that also support Depth Capture return {@linkcode MediaType | ['video', 'depth']}.
   */
  readonly mediaTypes: MediaType[]

  // pragma MARK: Focus
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports metering auto-focus ({@linkcode MeteringMode | 'AF'})
   * via {@linkcode CameraController.focusTo | focusTo(...)}.
   */
  readonly supportsFocusMetering: boolean
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports manual focus via
   * {@linkcode CameraController.setFocusLocked | setFocusLocked(...)}.
   */
  readonly supportsFocusLocking: boolean
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports smoothly adjusting focus via
   * {@linkcode CameraControllerConfiguration.enableSmoothAutoFocus}.
   *
   * @example
   * ```ts
   * await controller.configure({
   *   enableSmoothAutoFocus: device.supportsSmoothAutoFocus
   * })
   * ```
   */
  readonly supportsSmoothAutoFocus: boolean

  // pragma MARK: Exposure
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports metering auto-exposure ({@linkcode MeteringMode | 'AE'})
   * via {@linkcode CameraController.focusTo | focusTo(...)}.
   */
  readonly supportsExposureMetering: boolean
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports manual exposure via
   * {@linkcode CameraController.setExposureLocked | setExposureLocked(...)}.
   */
  readonly supportsExposureLocking: boolean
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports biasing auto-exposure via
   * {@linkcode CameraController.setExposureBias | setExposureBias(...)}.
   */
  readonly supportsExposureBias: boolean
  /**
   * Gets the minimum supported value for
   * the exposure bias (in EV units), or `0`
   * if {@linkcode supportsExposureBias} is false.
   */
  readonly minExposureBias: number
  /**
   * Gets the maximum supported value for
   * the exposure bias (in EV units), or `0`
   * if {@linkcode supportsExposureBias} is false.
   */
  readonly maxExposureBias: number

  // pragma MARK: White Balance
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports metering auto-white-balance ({@linkcode MeteringMode | 'AWB'})
   * via {@linkcode CameraController.focusTo | focusTo(...)}.
   */
  readonly supportsWhiteBalanceMetering: boolean
  /**
   * The maximum value a single color-channel can be
   * set to in {@linkcode WhiteBalanceGains}, or `0`
   * if {@linkcode supportsWhiteBalanceLocking} is `false`.
   */
  readonly maxWhiteBalanceGain: number
  /**
   * Gets whether this {@linkcode CameraDevice}
   * supports manual white-balance via
   * {@linkcode CameraController.setWhiteBalanceLocked | setWhiteBalanceLocked(...)}.
   *
   * @see {@linkcode maxWhiteBalanceGain}
   */
  readonly supportsWhiteBalanceLocking: boolean

  // pragma MARK: Flash
  /**
   * Whether this {@linkcode CameraDevice} has a
   * physical flash unit, or not.
   *
   * For {@linkcode CameraPosition | front}-facing
   * devices, {@linkcode hasFlash} may be `false`,
   * but you might still be able to use a screen-
   * flash for photo capture.
   *
   * @see {@linkcode CapturePhotoSettings.flashMode}
   */
  readonly hasFlash: boolean
  /**
   * Whether this {@linkcode CameraDevice} supports
   * using its flash ({@linkcode hasFlash}) as a
   * continuous light (_"torch"_) while the session is running.
   *
   * In almost all cases, {@linkcode hasTorch} is the
   * same as {@linkcode hasFlash}.
   *
   * Use {@linkcode supportsTorchStrength} to find out whether
   * the torch additionally accepts a custom brightness level
   * via {@linkcode CameraController.enableTorchWithStrength | enableTorchWithStrength(...)}.
   *
   * @see {@linkcode CameraController.setTorchMode | setTorchMode(...)}
   * @see {@linkcode supportsTorchStrength}
   */
  readonly hasTorch: boolean
  /**
   * Whether this {@linkcode CameraDevice} supports configuring
   * a custom torch brightness level via
   * {@linkcode CameraController.enableTorchWithStrength | enableTorchWithStrength(...)}.
   *
   * @discussion
   * Many devices with a {@linkcode hasTorch | torch} can only
   * toggle the torch on or off and do not expose a brightness
   * level - calling
   * {@linkcode CameraController.enableTorchWithStrength | enableTorchWithStrength(...)}
   * on such a device will fail. Use
   * {@linkcode CameraController.setTorchMode | setTorchMode('on')}
   * instead to turn the torch on at the system default level.
   *
   * @see {@linkcode hasTorch}
   * @see {@linkcode CameraController.enableTorchWithStrength | enableTorchWithStrength(...)}
   */
  readonly supportsTorchStrength: boolean
  /**
   * Gets the minimum supported torch strength for this device,
   * or `0` if {@linkcode supportsTorchStrength} is false.
   *
   * Use {@linkcode CameraController.enableTorchWithStrength | enableTorchWithStrength(...)}
   * to set the torch strength.
   *
   * @see {@linkcode supportsTorchStrength}
   * @see {@linkcode maxTorchStrength}
   */
  readonly minTorchStrength: number
  /**
   * Gets the maximum supported torch strength for this device,
   * or `0` if {@linkcode supportsTorchStrength} is false.
   *
   * Use {@linkcode CameraController.enableTorchWithStrength | enableTorchWithStrength(...)}
   * to set the torch strength.
   *
   * @see {@linkcode supportsTorchStrength}
   * @see {@linkcode minTorchStrength}
   */
  readonly maxTorchStrength: number

  // pragma MARK: Low Light Boost
  /**
   * Whether this {@linkcode CameraDevice} supports
   * enabling low-light boost to improve exposure in
   * dark scenes via {@linkcode CameraControllerConfiguration.enableLowLightBoost}.
   *
   * @example
   * ```ts
   * await controller.configure({
   *   enableLowLightBoost: device.supportsLowLightBoost
   * })
   * ```
   */
  readonly supportsLowLightBoost: boolean

  // pragma MARK: Zoom
  /**
   * The minimum individually supported zoom value of this device.
   *
   * @note Depending on the {@linkcode Constraint}s and
   * {@linkcode CameraOutput}s configured on the
   * {@linkcode CameraSession}, the actual minimum zoom
   * value may change.
   * Inspect the returned {@linkcode CameraController}'s
   * {@linkcode CameraController.minZoom | minZoom} property
   * for a true current minimum.
   */
  readonly minZoom: number
  /**
   * The maximum individually supported zoom value of this device.
   *
   * @note Depending on the {@linkcode Constraint}s and
   * {@linkcode CameraOutput}s configured on the
   * {@linkcode CameraSession}, the actual maximum zoom
   * value may change.
   * Inspect the returned {@linkcode CameraController}'s
   * {@linkcode CameraController.maxZoom | maxZoom} property
   * for a true current maximum.
   */
  readonly maxZoom: number
  /**
   * If this {@linkcode CameraDevice} is a virtual device,
   * this returns a list of zoom factors at which the virtual
   * device may switch to another physical camera.
   *
   * For physical devices, this returns an empty array.
   *
   * @example
   * ```ts
   * const camera = ...           // Triple-Camera (0.5x, 1x, 3x)
   * camera.zoomLensSwitchFactors // [1, 3]
   * ```
   */
  readonly zoomLensSwitchFactors: number[]
  /**
   * Whether the {@linkcode CameraDevice} supports
   * distortion correction to correct stretched edges
   * in wide-angle Cameras during Photo capture.
   * @see {@linkcode CapturePhotoSettings.enableDistortionCorrection}
   */
  readonly supportsDistortionCorrection: boolean

  /**
   * Returns whether the given {@linkcode CameraSessionConfig}
   * is supported by this {@linkcode CameraDevice}, or not.
   *
   * If this method returns `false`, configuring a
   * {@linkcode CameraSession} with this {@linkcode CameraSessionConfig}
   * will fail.
   */
  isSessionConfigSupported(config: CameraSessionConfig): boolean
}
