<template>
  <div
    class="file-upload"
    :class="{
      'label-position-top': labelPosition === 'top',
      'label-position-left': labelPosition === 'left'
      ,'file-upload--has-error': error
    }"
    :style="{
      '--file-upload-width': width || 'auto',
      '--file-upload-height': height || 'auto',
      '--file-upload-color': error
        ? 'var(--error-text-color)'
        : color || 'var(--upload-text-color, #888888)',
      '--file-upload-disabled-color': 'color-mix(in srgb, var(--file-upload-color), white 70%)',
      '--file-upload-background-color': bgColor || 'var(--upload-bg-color, #ffffff)',
      '--file-upload-background-active-color':
        activeColor || 'var(--upload-active-bg-color, #cccccc)',
      '--file-upload-border-color': borderColor || 'var(--upload-border-color, #888888)',
      '--file-upload-icon-color': iconColor || 'var(--file-upload-color, #888888)',
      '--file-upload-progress-color': progressColor || 'var(--progress-color, #4444aa)',
      '--file-upload-error-color': 'var(--error-text-color, #aa0000)',
      '--file-upload-success-color': 'var(--success-text-color, #00aa00)',
    }"
  >
    <label class="file-upload-label" v-if="label" for="fileInput">{{ label }}</label>
    <div
      class="upload-area"
      :class="{ 'is-dragging': isDragging, 'has-files': files.length > 0, 'is-disabled': disabled, 'is-error': error }"
    >
      <input
        ref="fileInput"
        type="file"
        :multiple="multiple"
        class="file-input"
        @change="handleFileSelect"
        :accept="accept"
        :required="required"
        :disabled="disabled"
        :placeholder="placeholder"
        @dragenter.prevent="handleDragEnter"
        @dragleave.prevent="handleDragLeave"
        @dragover.prevent
        @drop.prevent="handleDrop"
      />
      <div class="upload-content">
        <font-awesome-icon v-if="icon" :icon="['fas', icon]" />
        <p v-if="files.length === 0">
          {{ placeholder || 'Drag & drop files here or click to select' }}
          <div v-if="maxSize" class="max-size">(max size: {{ maxSize }}MB)</div>
        </p>
        <div v-else class="selected-files">
          <p>{{ files.length }} file(s) selected</p>
          <div v-for="(file, index) in files" :key="index" class="file-info">
            <span class="file-name">{{ file.name }}</span>
            <span class="file-size">{{ formatFileSize(file.size) }}</span>
          </div>
        </div>
      </div>
      <div v-if="error" class="error-message">{{ error }}</div>
      <div v-if="uploadStatus" class="status-message" :class="uploadStatus.type">
        {{ uploadStatus.message }}
      </div>
      <div v-if="uploadProgress > 0 && uploadProgress < 100" class="progress-bar">
        <div class="progress" :style="{ width: `${uploadProgress}%` }"></div>
      </div>
    </div>
    <button
      v-if="files.length > 0 && !uploadUrl && !disabled && showUploadButton"
      class="upload-button"
      @click="handleStartUpload"
    >
      Upload Files
    </button>
  </div>
</template>

<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import type {
  FileUploadProps,
  FileUploadEmits,
  UploadStatus,
  UploadCallbackResponse,
} from '@/types/fileupload'

const props = defineProps<FileUploadProps>()
const emit = defineEmits<FileUploadEmits>()

const fileInput = ref<HTMLInputElement | null>(null)
const files = ref<File[]>([])
const isDragging = ref(false)
const startUpload = ref(props.doUpload)
const uploadProgress = ref(0)
const error = ref('')
const uploadStatus = ref<UploadStatus>({
  type: 'pending',
  message: '',
})

onMounted(() => {
  startUpload.value = props.doUpload
  error.value = props.error || ''
})

const formatFileSize = (bytes: number): string => {
  if (bytes === 0) return '0 Bytes'
  const k = 1024
  const sizes = ['Bytes', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

const validateFileSize = (file: File): boolean => {
  if (props.maxSize && file.size > props.maxSize * 1024 * 1024) {
    error.value = `File "${file.name}" exceeds the maximum size of ${props.maxSize}MB`
    return false
  }
  return true
}

const handleDragEnter = () => {
  isDragging.value = true
}

const handleDragLeave = () => {
  isDragging.value = false
}

const handleDrop = (e: DragEvent) => {
  isDragging.value = false
  error.value = ''
  if (e.dataTransfer?.files) {
    const newFiles = Array.from(e.dataTransfer.files)

    // Reject if multiple files dropped but multiple is disabled
    if (!props.multiple && newFiles.length > 1) {
      error.value = 'Only one file can be uploaded at a time'
      return
    }

    if (newFiles.every(validateFileSize)) {
      files.value = [...files.value, ...newFiles]
    }
  }
}

const handleFileSelect = (e: Event) => {
  error.value = ''
  const input = e.target as HTMLInputElement
  if (input.files) {
    const newFiles = Array.from(input.files)
    if (newFiles.every(validateFileSize)) {
      files.value = [...files.value, ...newFiles]
    }
  }
  input.value = ''
}

const uploadFiles = async () => {
  if (!props.uploadUrl && !props.uploadCallback && !props.perFileCallback) {
    error.value = 'No upload URL or callback provided'
    return
  }
  if (files.value.length === 0) {
    error.value = 'No files selected'
    return
  }

  emit('upload-started', files.value)

  if (props.perFileCallback) {
    await uploadFilesViaPerFileCallback()
    return
  }

  // Use existing uploadCallback or default behavior
  const formData = new FormData()
  files.value.forEach((file) => formData.append('files', file))

  if (props.uploadCallback) {
    props
      .uploadCallback(formData)
      .then((response: UploadCallbackResponse) => {
        const { formData, headers } = response
        doUploadFiles(formData, headers)
      })
      .catch((err) => {
        const errorMessage = err instanceof Error ? err.message : 'Upload failed'
        error.value = errorMessage
        uploadStatus.value = {
          type: 'error',
          message: errorMessage,
          response: err,
        }
        emit('upload-complete', files.value)
        emit('upload-error', uploadStatus.value)
      })
  } else {
    doUploadFiles(formData, {})
  }
}

const doUploadFiles = (formData: FormData, headers: Record<string, string>) => {
  try {
    const xhr = new XMLHttpRequest()

    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        uploadProgress.value = (e.loaded / e.total) * 100
        emit('upload-progress', uploadProgress.value)

        if (uploadProgress.value >= 100) {
          uploadStatus.value = {
            type: 'processing',
            message: 'Please wait...',
          }
        }
      }
    })

    xhr.addEventListener('load', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        const response = JSON.parse(xhr.response)
        uploadStatus.value = {
          type: 'success',
          message: 'Upload completed successfully',
          response: response,
        }

        emit('upload-complete', files.value)
        emit('upload-success', uploadStatus.value)
        files.value = []
        uploadProgress.value = 0
        startUpload.value = false
      } else {
        const errorMessage = xhr.statusText || 'Upload failed'
        error.value = errorMessage
        uploadStatus.value = {
          type: 'error',
          message: errorMessage,
          response: null,
        }
        emit('upload-complete', files.value)
        emit('upload-error', uploadStatus.value)
        files.value = []
        uploadProgress.value = 0
        startUpload.value = false
      }
    })

    xhr.addEventListener('error', () => {
      const errorMessage = xhr.statusText || 'Upload error'
      uploadStatus.value = {
        type: 'error',
        message: errorMessage,
        response: null,
      }
      emit('upload-complete', files.value)
      emit('upload-error', uploadStatus.value)
    })

    xhr.open('POST', props.uploadUrl || '')

    Object.entries(headers).forEach(([key, value]) => {
      xhr.setRequestHeader(key, value)
    })

    xhr.send(formData)
  } catch (err) {
    const errorMessage = err instanceof Error ? err.message : 'Upload failed'
    error.value = errorMessage
    uploadStatus.value = {
      type: 'error',
      message: errorMessage,
      response: err,
    }
    emit('upload-complete', files.value)
    emit('upload-error', uploadStatus.value)
  }
}

// per-file path (parallel via Promise.all, aggregate progress)
const uploadFilesViaPerFileCallback = async () => {
  const total = files.value.length
  let completed = 0

  const uploadPromises = files.value.map((file, index) =>
    props.perFileCallback!(file, { index, total }).then(() => {
      completed++
      uploadProgress.value = (completed / total) * 100
      emit('upload-progress', uploadProgress.value)
    })
  )

  try {
    await Promise.all(uploadPromises)
    uploadStatus.value = { type: 'success', message: 'Upload completed successfully', response: null }
    emit('upload-complete', files.value)
    emit('upload-success', uploadStatus.value)
  } catch (err) {
    // Handle first failure from Promise.all
    const anyErr = err as any
    const status = anyErr?.status
    const statusText = anyErr?.statusText
    const statusTextSuffix = statusText ? ' ' + statusText : ''
    const suffix = typeof status === 'number' ? ` (${status}${statusTextSuffix})` : ''
    uploadStatus.value = {
      type: 'error',
      message: `Upload failed${suffix}`,
      response: { status, statusText },
    }
    emit('upload-complete', files.value)
    emit('upload-error', uploadStatus.value)
  } finally {
    files.value = []
    uploadProgress.value = 0
    startUpload.value = false
  }
}

const handleStartUpload = () => {
  emit('start-upload', files.value)
}

// Watch for changes in files and automatically upload when files are selected
watch(files, (newFiles) => {
  if (newFiles.length > 0) {
    if ((props.uploadUrl || props.uploadCallback || props.perFileCallback) && props.doUpload) {
      uploadFiles()
    } else {
      emit('files-selected', newFiles)
    }
  }
})

watch(
  () => props.doUpload,
  (newDoUpload) => {
    if (files.value && newDoUpload) {
      startUpload.value = newDoUpload
    } else {
      emit('files-selected', files.value)
      startUpload.value = false
    }
  },
)

watch(
  () => startUpload.value,
  (newStartUpload) => {
    if (newStartUpload) {
      uploadFiles()
    }
  },
)

watch(
  () => props.error,
  (newError) => {
    error.value = newError || ''
  },
)
</script>

<style scoped>
.file-upload {
  width: var(--file-upload-width);
  height: var(--file-upload-height);
  margin: 0 auto;

  &.label-position-left {
    display: grid;
    grid-template-columns: 10em calc(100% - 10em);
    gap: 1rem;
  }
}

.upload-area {
  position: relative;
  border: 2px dashed var(--file-upload-border-color);
  border-radius: 0.75rem;
  padding: 2rem;
  text-align: center;
  cursor: pointer;
  transition: all 0.3s ease;
  background-color: var(--file-upload-background-color);
}

.upload-area.is-dragging {
  background-color: var(--file-upload-background-active-color);
}

.upload-area.has-files {
  background-color: var(--file-upload-background-active-color);
}

.upload-area.is-disabled {
  color: var(--file-upload-disabled-color);
  border-color: var(--file-upload-disabled-color);
  cursor: not-allowed;
}

.upload-area.is-error {
  border-color: var(--file-upload-error-color);
}

.file-input {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: 0;
}

.upload-area.is-disabled .file-input {
  cursor: not-allowed;
}

.upload-content {
  display: grid;
  grid-template-rows: 1fr 1fr;
  text-align: center;
  gap: 1rem;

  .max-size {
    font-size: 0.75rem;
    color: var(--file-upload-color);
  }
}

.upload-content :deep(svg) {
  font-size: 2rem;
  color: var(--file-upload-icon-color);
  margin: 0 auto;
}

.selected-files {
  width: 100%;
  text-align: left;
  max-height: 200px;
  overflow-y: auto;
  font-size: 0.75rem;
  color: var(--file-upload-color);
}

.file-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.125rem 0;
  border-radius: 0.25rem;
  gap: 0.5rem;
  font-size: 0.75rem;
}

.file-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-right: 0.5rem;
}

.file-size {
  font-size: 0.7rem;
  flex-shrink: 0;
}

.error-message {
  color: var(--file-upload-error-color);
  margin-top: 1rem;
  font-size: 0.875rem;
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
}

.progress-bar {
  height: 0.5rem;
  border-radius: 0.25rem;
  margin-top: 1rem;
  overflow: hidden;
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
}

.progress {
  height: 100%;
  background-color: var(--file-upload-progress-color);
  transition: width 0.3s ease;
}

.status-message {
  margin-top: 1rem;
  padding: 0.5rem;
  border-radius: 0.25rem;
  font-size: 0.875rem;
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
}

.status-message.success {
  color: var(--file-upload-success-color);
}

.status-message.error {
  color: var(--file-upload-error-color);
}

.upload-button {
  margin-top: 1rem;
  padding: 0.5rem 1rem;
  background-color: var(--file-upload-color);
  color: white;
  border: none;
  border-radius: 0.25rem;
  cursor: pointer;
  font-size: 0.875rem;
  transition: background-color 0.3s ease;
}

.upload-button:hover {
  background-color: var(--file-upload-color-light);
}
</style>
