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

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

export type CameraFacing = 'front' | 'back'

interface ImagePickerContextValue {
  openCamera: (cameraFacing?: CameraFacing) => Promise<File>
  openGallery: () => 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 {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
    }
  }, [reportInteraction])

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

      if (file && resolveRef.current) {
        // Report success based on the active operation
        if (activeOperationRef.current === 'gallery') {
          reportInteraction({
            interactionType: 'image_picker_success',
          })
        } else if (activeOperationRef.current === 'camera') {
          reportInteraction({
            interactionType: 'camera_success',
          })
        }

        resolveRef.current(file)

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

        cleanupCancelHandler()
      }

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

  const openGallery = useCallback(() => {
    return new Promise<File>((resolve, reject) => {
      rejectPendingPromise()
      cleanupCancelHandler()

      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: CameraFacing = 'back') => {
      return new Promise<File>((resolve, reject) => {
        rejectPendingPromise()
        cleanupCancelHandler()

        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>
  )
}
