import type { TFunction } from 'i18next'
import type { FileEntity } from './types'
import type { FileResponse } from '@/types/workflow'
import mime from 'mime'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { upload } from '@/service/base'
import { TransferMethod } from '@/types/app'
import { FileAppearanceTypeEnum } from './types'

/**
 * Get appropriate error message for file upload errors
 * @param error - The error object from upload failure
 * @param defaultMessage - Default error message to use if no specific error is matched
 * @param t - Translation function
 * @returns Localized error message
 */
export const getFileUploadErrorMessage = (error: any, defaultMessage: string, t: TFunction): string => {
  const errorCode = error?.response?.code

  if (errorCode === 'forbidden')
    return error?.response?.message

  if (errorCode === 'file_extension_blocked')
    return t('fileUploader.fileExtensionBlocked', { ns: 'common' })

  return defaultMessage
}

type FileUploadResponse = {
  created_at: number
  created_by: string
  extension: string
  id: string
  mime_type: string
  name: string
  preview_url: string | null
  size: number
  source_url: string
}
type FileUploadParams = {
  file: File
  onProgressCallback: (progress: number) => void
  onSuccessCallback: (res: FileUploadResponse) => void
  onErrorCallback: (error?: any) => void
}
type FileUpload = (v: FileUploadParams, isPublic?: boolean, url?: string) => void
export const fileUpload: FileUpload = ({
  file,
  onProgressCallback,
  onSuccessCallback,
  onErrorCallback,
}, isPublic, url) => {
  const formData = new FormData()
  formData.append('file', file)
  const onProgress = (e: ProgressEvent) => {
    if (e.lengthComputable) {
      const percent = Math.floor(e.loaded / e.total * 100)
      onProgressCallback(percent)
    }
  }

  upload({
    xhr: new XMLHttpRequest(),
    data: formData,
    onprogress: onProgress,
  }, isPublic, url)
    .then((res) => {
      onSuccessCallback(res as FileUploadResponse)
    })
    .catch((error) => {
      onErrorCallback(error)
    })
}

const additionalExtensionMap = new Map<string, string[]>([
  ['text/x-markdown', ['md']],
])

export const getFileExtension = (fileName: string, fileMimetype: string, isRemote?: boolean) => {
  let extension = ''
  let extensions = new Set<string>()
  if (fileMimetype) {
    const extensionsFromMimeType = mime.getAllExtensions(fileMimetype) || new Set<string>()
    const additionalExtensions = additionalExtensionMap.get(fileMimetype) || []
    extensions = new Set<string>([
      ...extensionsFromMimeType,
      ...additionalExtensions,
    ])
  }

  let extensionInFileName = ''
  if (fileName) {
    const fileNamePair = fileName.split('.')
    const fileNamePairLength = fileNamePair.length

    if (fileNamePairLength > 1) {
      extensionInFileName = fileNamePair[fileNamePairLength - 1].toLowerCase()
      if (extensions.has(extensionInFileName))
        extension = extensionInFileName
    }
  }
  if (!extension) {
    if (extensions.size > 0) {
      const firstExtension = extensions.values().next().value
      extension = firstExtension ? firstExtension.toLowerCase() : ''
    }
    else {
      extension = extensionInFileName
    }
  }

  if (isRemote)
    extension = ''

  return extension
}

export const getFileAppearanceType = (fileName: string, fileMimetype: string) => {
  const extension = getFileExtension(fileName, fileMimetype)

  if (extension === 'gif')
    return FileAppearanceTypeEnum.gif

  if (FILE_EXTS.image.includes(extension.toUpperCase()))
    return FileAppearanceTypeEnum.image

  if (FILE_EXTS.video.includes(extension.toUpperCase()))
    return FileAppearanceTypeEnum.video

  if (FILE_EXTS.audio.includes(extension.toUpperCase()))
    return FileAppearanceTypeEnum.audio

  if (extension === 'html')
    return FileAppearanceTypeEnum.code

  if (extension === 'pdf')
    return FileAppearanceTypeEnum.pdf

  if (extension === 'md' || extension === 'markdown' || extension === 'mdx')
    return FileAppearanceTypeEnum.markdown

  if (extension === 'xlsx' || extension === 'xls')
    return FileAppearanceTypeEnum.excel

  if (extension === 'docx' || extension === 'doc')
    return FileAppearanceTypeEnum.word

  if (extension === 'pptx' || extension === 'ppt')
    return FileAppearanceTypeEnum.ppt

  if (FILE_EXTS.document.includes(extension.toUpperCase()))
    return FileAppearanceTypeEnum.document

  return FileAppearanceTypeEnum.custom
}

export const getSupportFileType = (fileName: string, fileMimetype: string, isCustom?: boolean) => {
  if (isCustom)
    return SupportUploadFileTypes.custom

  const extension = getFileExtension(fileName, fileMimetype)
  for (const key in FILE_EXTS) {
    if ((FILE_EXTS[key]).includes(extension.toUpperCase()))
      return key
  }

  return ''
}

export const getProcessedFiles = (files: FileEntity[]) => {
  return files.filter(file => file.progress !== -1).map(fileItem => ({
    type: fileItem.supportFileType,
    transfer_method: fileItem.transferMethod,
    url: fileItem.url || '',
    upload_file_id: fileItem.uploadedId || '',
  }))
}

export const getProcessedFilesFromResponse = (files: FileResponse[]) => {
  return files.map((fileItem) => {
    let supportFileType = fileItem.type

    if (fileItem.filename && fileItem.mime_type) {
      const detectedTypeFromFileName = getSupportFileType(fileItem.filename, '')
      const detectedTypeFromMime = getSupportFileType('', fileItem.mime_type)

      if (detectedTypeFromFileName
        && detectedTypeFromMime
        && detectedTypeFromFileName === detectedTypeFromMime
        && detectedTypeFromFileName !== fileItem.type) {
        supportFileType = detectedTypeFromFileName
      }
    }

    return {
      id: fileItem.related_id,
      name: fileItem.filename,
      size: fileItem.size || 0,
      type: fileItem.mime_type,
      progress: 100,
      transferMethod: fileItem.transfer_method,
      supportFileType,
      uploadedId: fileItem.upload_file_id || fileItem.related_id,
      url: fileItem.url || fileItem.remote_url,
    }
  })
}

export const getFileNameFromUrl = (url: string) => {
  const urlParts = url.split('/')
  return urlParts[urlParts.length - 1] || ''
}

export const getSupportFileExtensionList = (allowFileTypes: string[], allowFileExtensions: string[]) => {
  if (allowFileTypes.includes(SupportUploadFileTypes.custom))
    return allowFileExtensions.map(item => item.slice(1).toUpperCase())

  return allowFileTypes.map(type => FILE_EXTS[type]).flat()
}

export const isAllowedFileExtension = (fileName: string, fileMimetype: string, allowFileTypes: string[], allowFileExtensions: string[]) => {
  return getSupportFileExtensionList(allowFileTypes, allowFileExtensions).includes(getFileExtension(fileName, fileMimetype).toUpperCase())
}

export const getFilesInLogs = (rawData: any) => {
  const result = Object.keys(rawData || {}).map((key) => {
    if (typeof rawData[key] === 'object' && rawData[key]?.dify_model_identity === '__dify__file__') {
      return {
        varName: key,
        list: getProcessedFilesFromResponse([rawData[key]]),
      }
    }
    if (Array.isArray(rawData[key]) && rawData[key].some(item => item?.dify_model_identity === '__dify__file__')) {
      return {
        varName: key,
        list: getProcessedFilesFromResponse(rawData[key]),
      }
    }
    return undefined
  }).filter(Boolean)
  return result
}

export const fileIsUploaded = (file: FileEntity) => {
  if (file.uploadedId)
    return true

  if (file.transferMethod === TransferMethod.remote_url && file.progress === 100)
    return true
}
