<template>
  <div
    :class="[
      'ivyforms-field ivyforms-field__file-upload',
      'ivyforms-field__file-upload_' + field.id,
      field.cssClasses,
      { 'is-readonly': !!field.readOnly },
    ]"
  >
    <IvyFormItem
      :label="field.hideLabel ? '' : field.label"
      :required="field.required"
      :error="error"
      :prop="field.type + '_' + field.fieldIndex"
      :show-info="false"
      :label-position="field.labelPosition === 'default' ? 'top' : field.labelPosition"
    >
      <IvyFileUploadDropzone
        :variant="uploadVariant"
        :disabled="isDisabled"
        :multiple="dropzoneMultiple"
        :accept="acceptAttr"
        :drag-prefix="dragPrefix"
        :hint-text="hintBelowDropzone"
        :choose-label="uploadChooseLabel"
        @files-selected="onFilesSelected"
      />
      <div
        v-if="chosenFiles.length"
        class="ivyforms-field__file-upload-cards ivyforms-flex ivyforms-flex-direction-column ivyforms-gap-12 ivyforms-mt-12"
      >
        <IvyFileUploadFileCard
          v-for="(file, idx) in chosenFiles"
          :key="`${file.name}-${idx}-${file.size}-${file.lastModified}`"
          :file-name="file.name"
          status="idle"
          removable
          highlight-name
          :remove-label="getLabel('file_upload_remove_file')"
          @remove="removeAt(idx)"
        />
      </div>
    </IvyFormItem>
  </div>
</template>

<script setup lang="ts">
import { computed, inject } from 'vue'
import type { Field as FieldType } from '@/types/field'
import { useLabels } from '@/composables/useLabels'
import {
  buildAcceptAttribute,
  filterFilesByExtension,
  filterFilesBySizeLimit,
  isFileWithinSizeLimit,
  resolveFileUploadChooseLabel,
  resolveFileUploadVariant,
} from '@/utils/fileUploadValidation'
import IvyFormItem from '@/views/_components/form/IvyFormItem.vue'
import IvyFileUploadDropzone from '@/views/_components/file-upload/IvyFileUploadDropzone.vue'
import IvyFileUploadFileCard from '@/views/_components/file-upload/IvyFileUploadFileCard.vue'
import { resolveFileUploadFieldConfig } from '@/types/file-upload'

const props = defineProps<{
  field: FieldType
  error?: string
  modelValue?: unknown
}>()

const emit = defineEmits<{
  'update:modelValue': [value: File[]]
}>()

const { getLabel } = useLabels()

const isFormDisabled = inject<boolean>('ivyFormDisabled', false)

const isDisabled = computed(() => !!props.field.readOnly || isFormDisabled)

const fieldKey = computed(() => props.field.type + '_' + props.field.fieldIndex)

const maxFileSizeMb = computed(() => (props.field as Record<string, unknown>).maxFileSizeMb)

const chosenFiles = computed<File[]>(() => {
  const v = props.modelValue
  if (Array.isArray(v)) {
    return v.filter((x): x is File => x instanceof File)
  }
  return []
})

const fieldRecord = computed(() => props.field as Record<string, unknown>)

// Re-resolve when Pro registers filters after mount (hooks.updateCounter).
const hooksRevision = computed(() => window.IvyForms?.api?.hooks?.updateCounter?.value ?? 0)

const uploadConfig = computed(() => {
  void hooksRevision.value
  return resolveFileUploadFieldConfig(props.field)
})

const uploadVariant = computed(() => resolveFileUploadVariant(fieldRecord.value))

const uploadChooseLabel = computed(() =>
  resolveFileUploadChooseLabel(fieldRecord.value, getLabel('file_upload_choose_files')),
)

const allowsMultiple = computed(() => uploadConfig.value.allowsMultiple)

const maxUploads = computed((): number | null => {
  if (!allowsMultiple.value) return 1
  const max = uploadConfig.value.maxUploadCount
  return max > 0 ? max : null
})

const dropzoneMultiple = computed(() => {
  const max = maxUploads.value
  if (max === 1) return false
  return allowsMultiple.value
})

const acceptAttr = computed(() => {
  const hint = uploadConfig.value.acceptHint
  if (typeof hint === 'string' && hint.trim() !== '') {
    return hint
  }
  return buildAcceptAttribute(fieldRecord.value.allowedFileExtensions)
})

const dragPrefix = computed(() => {
  const p = props.field.placeholder
  const t = typeof p === 'string' ? p.trim() : ''
  return t !== '' ? t : getLabel('file_upload_placeholder_drag_here')
})

const hintBelowDropzone = computed(() => {
  const d = props.field.description
  return typeof d === 'string' ? d.trim() : ''
})

const clearFieldError = inject<(fieldKey: string) => void>('clearFieldError', () => {})
const setFieldError = inject<(fieldKey: string, message: string) => void>('setFieldError', () => {})

function setValidationError(message: string) {
  if (message) {
    setFieldError(fieldKey.value, message)
  } else {
    clearFieldError(fieldKey.value)
  }
}

function normalizeFileList(fl: FileList | null): File[] {
  if (!fl || fl.length === 0) return []
  const files: File[] = []
  for (let i = 0; i < fl.length; i++) {
    files.push(fl[i])
  }
  return files
}

function applyExtensionFilter(files: File[]) {
  const accepted = filterFilesByExtension(files, fieldRecord.value.allowedFileExtensions)
  return {
    accepted,
    rejectedType: accepted.length < files.length,
  }
}

function applySizeFilter(files: File[]) {
  const accepted = filterFilesBySizeLimit(files, maxFileSizeMb.value)
  return {
    accepted,
    rejectedSize: accepted.length < files.length,
  }
}

function applyCountLimit(accepted: File[]) {
  const isSingleReplace = !allowsMultiple.value
  const base = isSingleReplace ? [] : [...chosenFiles.value]
  const max = maxUploads.value
  let remaining = max === null ? Number.POSITIVE_INFINITY : Math.max(0, max - base.length)
  const toAdd: File[] = []
  let rejectedCount = false

  for (const file of accepted) {
    if (remaining <= 0) {
      rejectedCount = true
      break
    }
    toAdd.push(file)
    remaining--
  }

  return { toAdd, rejectedCount }
}

function resolveNextFileState(toAdd: File[]) {
  const isSingleReplace = !allowsMultiple.value
  const base = isSingleReplace ? [] : [...chosenFiles.value]
  const keepExistingSingle = isSingleReplace && toAdd.length === 0 && chosenFiles.value.length > 0
  const next = keepExistingSingle ? [...chosenFiles.value] : [...base, ...toAdd]

  return { next, keepExistingSingle }
}

function resolveUploadError(flags: {
  rejectedCount: boolean
  rejectedType: boolean
  rejectedSize: boolean
  toAdd: File[]
}) {
  if (flags.rejectedCount) {
    setValidationError(getLabel('file_upload_error_too_many', 'Too many files.'))
  } else if (flags.rejectedType) {
    setValidationError(getLabel('file_upload_error_invalid_type'))
  } else if (flags.rejectedSize) {
    setValidationError(getLabel('file_upload_error_size'))
  } else if (flags.toAdd.length > 0) {
    setValidationError('')
  }
}

function onFilesSelected(fl: FileList | null) {
  const incoming = normalizeFileList(fl)
  if (incoming.length === 0) return

  const { accepted: afterExtension, rejectedType } = applyExtensionFilter(incoming)
  const { accepted, rejectedSize } = applySizeFilter(afterExtension)
  const { toAdd, rejectedCount } = applyCountLimit(accepted)
  const { next, keepExistingSingle } = resolveNextFileState(toAdd)

  resolveUploadError({ rejectedCount, rejectedType, rejectedSize, toAdd })

  if (!keepExistingSingle) {
    emit('update:modelValue', next)
  }
}

function removeAt(index: number) {
  const next = chosenFiles.value.filter((_, i) => i !== index)
  emit('update:modelValue', next)

  const stillInvalid = next.some((f) => !isFileWithinSizeLimit(f, maxFileSizeMb.value))
  if (!stillInvalid) {
    setValidationError('')
  }
}
</script>
