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

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

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
}

const isAndroid = () => window?.minisParams?.platform === 'android'

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 {requestPermission} = useRequestPermissions()

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

  const rejectPendingPromise = useCallback(() => {
    if (rejectRef.current) {
      rejectRef.current(
        new Error('New file picker opened before previous completed')
      )
      resolveRef.current = null
      rejectRef.current = null
    }
  }, [])

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

      if (file && resolveRef.current) {
        resolveRef.current(file)

        resolveRef.current = null
        rejectRef.current = null

        cleanupCancelHandler()
      }

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

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

      resolveRef.current = resolve
      rejectRef.current = reject

      const input = galleryInputRef.current

      if (!input) {
        reject(new Error('Gallery input not found'))
        resolveRef.current = null
        rejectRef.current = null
        return
      }

      const handleCancel = () => {
        if (rejectRef.current) {
          rejectRef.current(new Error('User cancelled file selection'))
          resolveRef.current = null
          rejectRef.current = null
        }
        cleanupCancelHandler()
      }

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

      if (isAndroid()) {
        // Android requires explicit camera permission for camera picker
        requestPermission({permission: 'CAMERA'})
          .then(() => {
            // This will show both Camera and Gallery
            input.click()
          })
          .catch(() => {
            // Show only Gallery
            input.click()
          })
      } else {
        input.click()
      }
    })
  }, [rejectPendingPromise, cleanupCancelHandler, requestPermission])

  const openCamera = useCallback(
    (cameraFacing: CameraFacing = 'back') => {
      return new Promise<File>((resolve, reject) => {
        rejectPendingPromise()
        cleanupCancelHandler()

        resolveRef.current = resolve
        rejectRef.current = reject

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

        if (!input) {
          reject(new Error('Camera input not found'))
          resolveRef.current = null
          rejectRef.current = null
          return
        }

        const handleCancel = () => {
          if (rejectRef.current) {
            rejectRef.current(new Error('User cancelled camera'))
            resolveRef.current = null
            rejectRef.current = null
          }
          cleanupCancelHandler()
        }

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

        if (isAndroid()) {
          // Android requires explicit camera permission
          requestPermission({permission: 'CAMERA'})
            .then(({granted}) => {
              if (granted) {
                input.click()
              } else {
                reject(new Error('Camera permission not granted'))
                resolveRef.current = null
                rejectRef.current = null
              }
            })
            .catch(() => {
              reject(new Error('Camera permission not granted'))
              resolveRef.current = null
              rejectRef.current = null
            })
        } else {
          input.click()
        }
      })
    },
    [rejectPendingPromise, cleanupCancelHandler, requestPermission]
  )

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