/*
 * Copyright (c) 2025-2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  Flex,
  Text,
  Column,
  Row,
  Scroll,
  SymbolGlyph,
  FileSelectorParam,
  FileSelectorResult,
  FileSelectorMode,
  AcceptableFileType,
  OnShowFileSelectorEvent,
  DismissDialogAction,
  DismissReason,
  $r,
  FontWeight,
  Margin,
  EdgeWidths,
  FlexAlign,
  FlexDirection,
  ItemAlign,
  TextAlign,
  Builder
} from '@ohos.arkui.component'
import { BusinessError } from '@ohos.base'
import { LengthMetrics } from 'arkui.Graphics'
import cameraPicker from '@ohos.multimedia.cameraPicker'
import camera from '@ohos.multimedia.camera'
import deviceInfo from '@ohos.deviceInfo'
import { UIContext } from '@ohos.arkui.UIContext'
import picker from '@ohos.file.picker'
import fileIo, { Watcher, WatchEventListener } from '@ohos.file.fs'
import fileUri from '@ohos.file.fileuri'

const defaultPublicPath = '/storage/Users/currentUser/'

const publicDirectoryMap = new Map<string, string>([
  ['desktop', defaultPublicPath + 'desktop'],
  ['documents', defaultPublicPath + 'documents'],
  ['downloads', defaultPublicPath + 'download'],
  ['music', defaultPublicPath + 'music'],
  ['pictures', defaultPublicPath + 'images'],
  ['videos', defaultPublicPath + 'videos'],
])

class SelectorDialog {
  private customDialogComponentId: int = 0

  defaultOnShowFileSelector(param: FileSelectorParam, result: FileSelectorResult) {
    console.info('defaultOnShowFileSelector implementation')

    const event: OnShowFileSelectorEvent = { fileSelector: param, result: result }
    const currentDevice = deviceInfo.deviceType.toLowerCase()
    if (needShowDialog(event)) {
      UIContext.resolveUIContext()?.getPromptAction()
        .openCustomDialog({
          builder: () => this.fileSelectorDialog(event),
          onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
            console.info('reason: ' + JSON.stringify(dismissDialogAction.reason))
            console.log('dialog onWillDismiss')
            if (dismissDialogAction.reason === DismissReason.PRESS_BACK) {
              event.result.handleFileList([])
              dismissDialogAction.dismiss()
            }
            if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) {
              event.result.handleFileList([])
              dismissDialogAction.dismiss()
            }
          }
        })
        .then((dialogId) => {
          this.customDialogComponentId = dialogId
        })
        .catch((error) => {
          event.result.handleFileList([])
          console.error(`openCustomDialog error code is ${error.code}, message is ${error.message}`)
        })
    } else if (currentDevice !== '2in1' && event.fileSelector.isCapture() &&
      (isContainImageMimeType(event.fileSelector.getAcceptType()) ||
      isContainVideoMimeType(event.fileSelector.getAcceptType()))) {
      console.log('takePhoto will be directly invoked due to the capture property')
      takePhoto(event)
    } else {
      console.log('selectFile will be invoked by web')
      selectFile(event)
    }
  }

  @Builder
  private fileSelectorDialog(event: OnShowFileSelectorEvent) {
    Flex({ justifyContent: FlexAlign.Center, direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
      Row() {
        Text($r('sys.string.choose_to_upload'))
          .fontSize('20vp')
          .fontWeight(FontWeight.Bold)
          .textAlign(TextAlign.Center)
      }
      .constraintSize({ minHeight: 56 })
      .width('calc(100% - 48vp)')
      .justifyContent(FlexAlign.Center)

      Scroll() {
        Column() {
          this.fileSelectorListItem('sys.symbol.picture', 'sys.string.gallery', selectPicture, event)
          const acceptTypes = event.fileSelector.getAcceptType()
          let cameraOption = 'sys.string.taking_photos_or_videos'
          if (isContainImageMimeType(acceptTypes) && !isContainVideoMimeType(acceptTypes)) {
            cameraOption = 'sys.string.taking_photos'
          }
          if (!isContainImageMimeType(acceptTypes) && isContainVideoMimeType(acceptTypes)) {
            cameraOption = 'sys.string.video_recording'
          }
          this.fileSelectorListItem('sys.symbol.camera', cameraOption, takePhoto, event)
          this.fileSelectorListItem('sys.symbol.doc_text', 'sys.string.document', selectFile, event)
        }
      }

      Row() {
        Text($r('sys.string.general_cancel'))
          .fontColor('#FF0A59F7')
          .fontSize('16vp')
          .fontWeight(FontWeight.Medium)
          .textAlign(TextAlign.Center)
      }
      .onClick((e) => {
        try {
          console.log('Get Alert Dialog handled')
          event.result.handleFileList([])
          UIContext.resolveUIContext()?.getPromptAction().closeCustomDialog(this.customDialogComponentId)
        } catch (error) {
          console.error(`closeCustomDialog error code is ${error.code}, message is ${error.message}`)
        }
      })
      .constraintSize({ minHeight: 40 })
      .margin({
        top: 8,
        bottom: 16
      } as Margin)
      .width('calc(100% - 32vp)')
      .justifyContent(FlexAlign.Center)
      .flexShrink(0)
    }
    .height('auto')
  }

  @Builder
  private fileSelectorListItem(sysResource: string, text: string, func: (e: OnShowFileSelectorEvent) => void,
    event: OnShowFileSelectorEvent) {
    Row() {
      SymbolGlyph($r(sysResource))
        .fontSize('24vp')
        .fontWeight(FontWeight.Medium)
        .margin({
          end: LengthMetrics.vp(16)
        })
        .fontColor([$r('sys.color.font_primary')])
      Row() {
        Text($r(text))
          .fontSize('16vp')
          .fontWeight(FontWeight.Medium)
      }
      .constraintSize({ minHeight: 56 })
      .width('calc(100% - 40vp)')
      .border({ width: { bottom: 0.5 } as EdgeWidths, color: '#33000000' })
    }
    .onClick((e) => {
      UIContext.resolveUIContext()?.getPromptAction().closeCustomDialog(this.customDialogComponentId)
      func(event)
    })
    .width('calc(100% - 48vp)')
  }
}

function needShowDialog(event: OnShowFileSelectorEvent) {
  let result = false
  try {
    const currentDevice = deviceInfo.deviceType.toLowerCase()
    if (currentDevice === '2in1') {
      return false
    }
    if (event.fileSelector.isCapture()) {
      console.log('input element contain capture tag, not show dialog')
      return false
    }
    const acceptTypes = event.fileSelector.getAcceptType()
    if (isContainImageMimeType(acceptTypes) || isContainVideoMimeType(acceptTypes)) {
      result = true
    }
  } catch (error) {
    console.log('show dialog happened error: ' + JSON.stringify(error))
  }
  return result
}

function isContainImageMimeType(acceptTypes: Array<string> | undefined) {
  if (!(acceptTypes instanceof Array)) {
    return false
  }
  if (acceptTypes.length < 1) {
    return true
  }

  const imageTypes = ['tif', 'xbm', 'tiff', 'pjp', 'jfif', 'bmp', 'avif', 'apng', 'ico',
    'webp', 'svg', 'gif', 'svgz', 'jpg', 'jpeg', 'png', 'pjpeg']
  for (const acceptType of acceptTypes) {
    for (const imageType of imageTypes) {
      if ((acceptType as string).includes(imageType)) {
        return true
      }
    }
  }
  return false
}

function isContainVideoMimeType(acceptTypes: Array<string> | undefined) {
  if (!(acceptTypes instanceof Array)) {
    return false
  }
  if (acceptTypes.length < 1) {
    return true
  }

  const videoTypes = ['ogm', 'ogv', 'mpg', 'mp4', 'mpeg', 'm4v', 'webm']
  for (const acceptType of acceptTypes) {
    for (const videoType of videoTypes) {
      if ((acceptType as string).includes(videoType)) {
        return true
      }
    }
  }
  return false
}

function selectPicture(event: OnShowFileSelectorEvent) {
  event.result.handleFileList([])
  UIContext.resolveUIContext()?.getPromptAction().showToast({ message: '无法打开图片功能,请检查是否具备图片功能' })
}

function takePhoto(event: OnShowFileSelectorEvent) {
  const pickerProfileOptions: cameraPicker.PickerProfile = {
    cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
  }
  const acceptTypes = event.fileSelector.getAcceptType()
  const mediaType: cameraPicker.PickerMediaType[] = []
  if (isContainImageMimeType(acceptTypes)) {
    mediaType.push(cameraPicker.PickerMediaType.PHOTO)
  }
  if (isContainVideoMimeType(acceptTypes)) {
    mediaType.push(cameraPicker.PickerMediaType.VIDEO)
  }
  const result: string[] = []
  cameraPicker.pick(UIContext.resolveUIContext()?.getHostContext()!, mediaType, pickerProfileOptions)
    .then((pickerResult) => {
      result.push(pickerResult.resultUri)
    })
    .catch((error) => {
      console.log('selectFile error: ' + JSON.stringify(error))
      UIContext.resolveUIContext()?.getPromptAction().showToast({
        message: '无法打开拍照功能,请检查是否具备拍照功能'
      })
    })
    .finally(() => {
      event.result.handleFileList(result)
    })
}

function selectFile(event: OnShowFileSelectorEvent) {
  const documentPicker = new picker.DocumentViewPicker(UIContext.resolveUIContext()?.getHostContext()!)
  let result: string[] = []
  if (event.fileSelector.getMode() !== FileSelectorMode.FILE_SAVE_MODE) {
    documentPicker.select(createDocumentSelectionOptions(event.fileSelector)).then((documentSelectResult) => {
      result = documentSelectResult
    }).catch((error) => {
      console.log('selectFile error: ' + JSON.stringify(error))
      UIContext.resolveUIContext()?.getPromptAction().showToast({
        message: '无法打开文件功能,请检查是否具备文件功能'
      })
    }).finally(() => {
      event.result.handleFileList(result)
    })
  } else {
    documentPicker.save(createDocumentSaveOptions(event.fileSelector)).then((documentSaveResult) => {
      const filePaths: string[] = documentSaveResult
      let tempUri = ''
      if (filePaths.length > 0) {
        const fileName = filePaths[0].substr(filePaths[0].lastIndexOf('/'))
        const tempPath = UIContext.resolveUIContext()?.getHostContext()!.filesDir + fileName
        tempUri = fileUri.getUriFromPath(tempPath)
        const randomAccessFile = fileIo.createRandomAccessFileSync(tempPath, fileIo.OpenMode.CREATE)
        randomAccessFile.close()

        let watcher: Watcher | undefined = undefined
        const listener: WatchEventListener = () => {
          fileIo.copy(tempUri, filePaths[0]).then(() => {
            console.log('Web save file succeeded in copying.')
            fileIo.unlink(tempPath)
          }).catch((err) => {
            console.error(`Web save file failed to copy: ${JSON.stringify(err)}`)
          }).finally(() => {
            watcher?.stop()
          })
        }
        watcher = fileIo.createWatcher(tempPath, 0x4, listener)
        watcher.start()
      }
      result.push(tempUri)
    }).catch((error) => {
      console.log('saveFile error: ' + JSON.stringify(error))
      UIContext.resolveUIContext()?.getPromptAction().showToast({
        message: '无法打开文件功能,请检查是否具备文件功能'
      })
    }).finally(() => {
      event.result.handleFileList(result)
    })
  }
}

function createDocumentSelectionOptions(param: FileSelectorParam) {
  const documentSelectOptions: picker.DocumentSelectOptions = {}
  const currentDevice = deviceInfo.deviceType.toLowerCase()
  try {
    const defaultSelectNumber = 500
    const defaultSelectMode = picker.DocumentSelectMode.MIXED
    documentSelectOptions.maxSelectNumber = defaultSelectNumber
    documentSelectOptions.selectMode = defaultSelectMode
    documentSelectOptions.defaultFilePathUri = getDefaultPath(param)
    const mode = param.getMode()
    switch (mode) {
      case FileSelectorMode.FILE_OPEN_MODE:
        documentSelectOptions.maxSelectNumber = 1
        documentSelectOptions.selectMode = picker.DocumentSelectMode.FILE
        break
      case FileSelectorMode.FILE_OPEN_MULTIPLE_MODE:
        documentSelectOptions.selectMode = picker.DocumentSelectMode.FILE
        break
      case FileSelectorMode.FILE_OPEN_FOLDER_MODE:
        documentSelectOptions.selectMode = picker.DocumentSelectMode.FOLDER
        break
      default:
        break
    }
    documentSelectOptions.fileSuffixFilters = []
    const suffix = param.getAcceptType().join(',')
    const accepts = param.getAcceptableFileTypes()
    const descriptions = param.getDescriptions()
    if (accepts && accepts.length > 0) {
      suffixFromAccepts(documentSelectOptions.fileSuffixFilters!, descriptions, accepts)
    } else if (suffix) {
      documentSelectOptions.fileSuffixFilters!.push(suffix)
    }
    if (currentDevice !== 'phone' && !param.isAcceptAllOptionExcluded()) {
      documentSelectOptions.fileSuffixFilters!.push('.*')
    }
  } catch (error) {
    console.log('selectFile error: ' + JSON.stringify(error))
  }
  return documentSelectOptions
}

function createDocumentSaveOptions(param: FileSelectorParam) {
  const documentSaveOptions: picker.DocumentSaveOptions = {}
  const currentDevice = deviceInfo.deviceType.toLowerCase()
  try {
    documentSaveOptions.pickerMode = picker.DocumentPickerMode.DEFAULT
    documentSaveOptions.fileSuffixChoices = []
    documentSaveOptions.newFileNames = [param.getSuggestedName()]
    documentSaveOptions.defaultFilePathUri = getDefaultPath(param)
    const suffix = param.getAcceptType().join(',')
    const accepts = param.getAcceptableFileTypes()
    const descriptions = param.getDescriptions()
    if (accepts && accepts.length > 0) {
      suffixFromAccepts(documentSaveOptions.fileSuffixChoices!, descriptions, accepts)
    } else if (suffix) {
      documentSaveOptions.fileSuffixChoices!.push(suffix)
    }
    if (currentDevice !== 'phone' && !param.isAcceptAllOptionExcluded()) {
      documentSaveOptions.fileSuffixChoices!.push('.*')
    }
  } catch (error) {
    console.log('saveFile error: ' + JSON.stringify(error))
  }
  return documentSaveOptions
}

function getDefaultPath(param: FileSelectorParam) {
  let path = param.getDefaultPath()
  if (publicDirectoryMap.get(path) !== undefined) {
    path = publicDirectoryMap.get(path)!
  }
  return fileUri.getUriFromPath(path)
}

function suffixFromAccepts(suffix: string[], descriptions: string[], accepts: AcceptableFileType[][]) {
  const n = accepts.length
  for (let i = 0; i < n; i++) {
    const m = accepts[i].length
    const extList: string[] = []
    for (let j = 0; j < m; j++) {
      extList.push(accepts[i][j].acceptableType.join(','))
    }
    const ext = extList.join(',')
    const desc = descriptions[i] + '(' + ext + ')' + '|'
    suffix.push(desc + ext)
  }
}