import React, {
  createContext,
  useContext,
  useRef,
  useCallback,
  useEffect,
  useMemo,
} from 'react'

import {useRequestPermissions} from '../hooks/util/useRequestPermissions'
import {useReportInteraction} from '../internal/useReportInteraction'
import {resizeImage} from '../internal/utils/resizeImage'

/** @publicDocs */
export type ImageQuality = 'low' | 'medium' | 'high' | 'original'
/** @publicDocs */
export type CameraFacing = 'front' | 'back'
/** @publicDocs */
export interface CustomImageQuality {
  size?: number
  compression?: number
}

/** @publicDocs */
export interface OpenCameraParams {
  cameraFacing?: CameraFacing
  quality?: ImageQuality
  customQuality?: CustomImageQuality
}

/** @publicDocs */
export interface OpenGalleryParams {
  quality?: ImageQuality
  customQuality?: CustomImageQuality
}

interface ImagePickerContextValue {
  openCamera: (params?: OpenCameraParams) => Promise<File>
  openGallery: (params?: OpenGalleryParams) => Promise<File>
}

const ImagePickerContext = createContext<ImagePickerContextValue | null>(null)

export function useImagePickerContext() {
  const context = useContext(ImagePickerContext)
  if (!context) {
    throw new Error(
      'useImagePickerContext must be used within an ImagePickerProvider'
    )
  }
  return context
}

interface ImagePickerProviderProps {
  children: React.ReactNode
}

export function ImagePickerProvider({children}: ImagePickerProviderProps) {
  const galleryInputRef = useRef<HTMLInputElement>(null)
  const frontCameraInputRef = useRef<HTMLInputElement>(null)
  const backCameraInputRef = useRef<HTMLInputElement>(null)
  const resolveRef = useRef<((file: File) => void) | null>(null)
  const rejectRef = useRef<((reason: Error) => void) | null>(null)
  const activeCancelHandlerRef = useRef<{
    input: HTMLInputElement
    handler: () => void
  } | null>(null)
  const activeOperationRef = useRef<'gallery' | 'camera' | null>(null)
  const qualityRef = useRef<ImageQuality>('medium')
  const customQualityRef = useRef<CustomImageQuality>(undefined)
  const {requestPermission} = useRequestPermissions()
  const {reportInteraction} = useReportInteraction()

  const cleanupCancelHandler = useCallback(() => {
    if (activeCancelHandlerRef.current) {
      const {input, handler} = activeCancelHandlerRef.current
      input.removeEventListener('cancel', handler)
      activeCancelHandlerRef.current = null
    }
  }, [])

  const rejectPendingPromise = useCallback(() => {
    if (rejectRef.current) {
      const error = new Error(
        'New file picker opened before previous completed'
      )
      if (activeOperationRef.current === 'gallery') {
        reportInteraction({
          interactionType: 'image_picker_error',
          interactionValue: error.message,
        })
      } else if (activeOperationRef.current === 'camera') {
        reportInteraction({
          interactionType: 'camera_error',
          interactionValue: error.message,
        })
      }

      rejectRef.current(error)
      resolveRef.current = null
      rejectRef.current = null
      activeOperationRef.current = null
      qualityRef.current = 'medium'
      customQualityRef.current = undefined
    }
  }, [reportInteraction])

  const handleFileChange = useCallback(
    async (event: React.ChangeEvent<HTMLInputElement>) => {
      const {target} = event
      const file = target.files?.[0]

      if (file && resolveRef.current) {
        try {
          const resizedFile = await resizeImage({
            file,
            quality: qualityRef.current,
            customQuality: customQualityRef.current,
          })

          if (activeOperationRef.current === 'gallery') {
            reportInteraction({
              interactionType: 'image_picker_success',
            })
          } else if (activeOperationRef.current === 'camera') {
            reportInteraction({
              interactionType: 'camera_success',
            })
          }

          resolveRef.current(resizedFile)
        } catch (error) {
          console.warn('Image resize failed, using original:', error)
          if (resolveRef.current) {
            resolveRef.current(file)
          }
        }

        resolveRef.current = null
        rejectRef.current = null
        activeOperationRef.current = null

        cleanupCancelHandler()
      }

      target.value = ''
    },
    [cleanupCancelHandler, reportInteraction]
  )

  const openGallery = useCallback(
    ({quality = 'medium', customQuality}: OpenGalleryParams = {}) => {
      return new Promise<File>((resolve, reject) => {
        rejectPendingPromise()
        cleanupCancelHandler()

        qualityRef.current = quality
        customQualityRef.current = customQuality
        resolveRef.current = resolve
        rejectRef.current = reject
        activeOperationRef.current = 'gallery'
        const input = galleryInputRef.current

        if (!input) {
          const error = new Error('Gallery input not found')
          reportInteraction({
            interactionType: 'image_picker_error',
            interactionValue: error.message,
          })
          reject(error)
          resolveRef.current = null
          rejectRef.current = null
          activeOperationRef.current = null
          return
        }

        const handleCancel = () => {
          if (rejectRef.current) {
            const error = new Error('User cancelled file selection')
            reportInteraction({
              interactionType: 'image_picker_error',
              interactionValue: error.message,
            })
            rejectRef.current(error)
            resolveRef.current = null
            rejectRef.current = null
            activeOperationRef.current = null
          }
          cleanupCancelHandler()
        }

        input.addEventListener('cancel', handleCancel)
        activeCancelHandlerRef.current = {input, handler: handleCancel}

        reportInteraction({
          interactionType: 'image_picker_open',
        })

        requestPermission({permission: 'CAMERA'})
          .then(() => {
            // This will show both Camera and Gallery
            input.click()
          })
          .catch(() => {
            // Show only Gallery
            input.click()
          })
      })
    },
    [
      rejectPendingPromise,
      cleanupCancelHandler,
      requestPermission,
      reportInteraction,
    ]
  )

  const openCamera = useCallback(
    ({
      cameraFacing = 'back',
      quality = 'medium',
      customQuality,
    }: OpenCameraParams = {}) => {
      return new Promise<File>((resolve, reject) => {
        rejectPendingPromise()
        cleanupCancelHandler()

        qualityRef.current = quality
        customQualityRef.current = customQuality
        resolveRef.current = resolve
        rejectRef.current = reject
        activeOperationRef.current = 'camera'

        const input =
          cameraFacing === 'front'
            ? frontCameraInputRef.current
            : backCameraInputRef.current

        if (!input) {
          const error = new Error('Camera input not found')
          reportInteraction({
            interactionType: 'camera_error',
            interactionValue: error.message,
          })
          reject(error)
          resolveRef.current = null
          rejectRef.current = null
          activeOperationRef.current = null
          return
        }

        const handleCancel = () => {
          if (rejectRef.current) {
            const error = new Error('User cancelled camera')
            reportInteraction({
              interactionType: 'camera_error',
              interactionValue: error.message,
            })
            rejectRef.current(error)
            resolveRef.current = null
            rejectRef.current = null
            activeOperationRef.current = null
          }
          cleanupCancelHandler()
        }

        input.addEventListener('cancel', handleCancel)
        activeCancelHandlerRef.current = {input, handler: handleCancel}

        reportInteraction({
          interactionType: 'camera_open',
        })

        requestPermission({permission: 'CAMERA'})
          .then(({granted}) => {
            if (granted) {
              input.click()
            } else {
              const error = new Error('Camera permission not granted')
              reportInteraction({
                interactionType: 'camera_error',
                interactionValue: error.message,
              })
              reject(error)
              resolveRef.current = null
              rejectRef.current = null
              activeOperationRef.current = null
            }
          })
          .catch(() => {
            const error = new Error('Camera permission not granted')
            reject(error)
            resolveRef.current = null
            rejectRef.current = null
            activeOperationRef.current = null
          })
      })
    },
    [
      rejectPendingPromise,
      cleanupCancelHandler,
      requestPermission,
      reportInteraction,
    ]
  )

  useEffect(() => {
    return () => {
      rejectPendingPromise()
      cleanupCancelHandler()
    }
  }, [rejectPendingPromise, cleanupCancelHandler])

  const contextValue: ImagePickerContextValue = useMemo(
    () => ({
      openCamera,
      openGallery,
    }),
    [openCamera, openGallery]
  )

  return (
    <ImagePickerContext.Provider value={contextValue}>
      {children}
      <input
        ref={galleryInputRef}
        type="file"
        accept="image/*"
        onChange={handleFileChange}
        style={{display: 'none'}}
        aria-hidden="true"
      />
      <input
        ref={frontCameraInputRef}
        type="file"
        accept="image/*"
        capture="user"
        onChange={handleFileChange}
        style={{display: 'none'}}
        aria-hidden="true"
      />
      <input
        ref={backCameraInputRef}
        type="file"
        accept="image/*"
        capture="environment"
        onChange={handleFileChange}
        style={{display: 'none'}}
        aria-hidden="true"
      />
    </ImagePickerContext.Provider>
  )
}
