<template>
  <div class="comment-attachment column full-width shadow-1 q-pa-sm  bg-white" :data-id="id">
    <div>{{ title || label }}</div>

    <div class="row q-ml-md full-width items-center no-wrap">
      <div class="col q-ml-md">
        <q-file
          ref="fileRef"
          :model-value="files"
          @update:model-value="updateFiles"
          :disable="!!disable"
          accept=".pdf,.docx,.txt,.md"
          dense
          borderless
          hide-bottom-space
          :clearable="false"
          input-style="display: none"
          style="max-width: 400px"
        >
          <template v-slot:prepend>
            <div class="row items-center no-wrap full-width">
              <template v-if="!files">
                <q-btn
                  round
                  dense
                  flat
                  color="primary"
                  icon="attach_file"
                  :disable="!!disable || isUploading"
                  @click.stop.prevent="fileRef?.pickFiles()"
                >
                  <q-tooltip>{{ t('pickFiles') }}</q-tooltip>
                </q-btn>
              </template>

              <template v-else>
                <q-btn
                  round
                  dense
                  flat
                  color="primary"
                  icon="cloud_upload"
                  :disable="!!disable || !canUpload || isUploading"
                  :loading="isUploading"
                  @click.stop.prevent="upload"
                />
              </template>

              <q-chip
                v-if="chipLabel"
                dense
                :removable="!disable"
                class="q-ml-sm comment-attachment__file-chip"
                :color="isUploaded ? 'positive' : undefined"
                :text-color="isUploaded ? 'white' : undefined"
                @remove="onChipRemove"
              >
                <span class="ellipsis">{{ chipLabel }}</span>
              </q-chip>
            </div>
          </template>

        </q-file>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useQuasar, type QFile } from 'quasar'
import { useUserstore } from '../stores/userstore'
import { ApiError, deleteAttachment, loadAttachments, uploadDocument } from '../utils/api'

export interface CommentAttachmentData {
  comments: string
  attachments: string[]
}

type Payload = CommentAttachmentData

const props = defineProps<{
  modelValue?: Partial<Payload> | null
  label?: string
  title?: string
  session?: string
  id?: string
  autoload?: boolean
  disable?: boolean
}>()
const emit = defineEmits<{
  (e: 'update:modelValue', v: Payload | null): void
}>()

const { t } = useI18n()
const $q = useQuasar()
const userStore = useUserstore()
const fileRef = ref<QFile | null>(null)
const initialAttachments = props.modelValue?.attachments

const local = reactive<Payload>({
  comments: props.modelValue?.comments || '',
  attachments: Array.isArray(initialAttachments) ? [...initialAttachments] : [],
})

const comments = ref(local.comments)

type UploadProgressItem = {
  percent: number
  color: string
  icon: string
}

const files = ref<File | null>(null)
const isUploading = ref(false)
const uploadProgress = ref<UploadProgressItem[]>([])

const allowedExtensions = new Set(['pdf', 'docx', 'txt', 'md'])
const allowedLabel = '.pdf, .docx, .txt, .md'

function isAllowedFile(file: File): boolean {
  const name = (file.name || '').trim().toLowerCase()
  const ext = name.includes('.') ? name.split('.').pop() : ''
  return !!ext && allowedExtensions.has(ext)
}

const canUpload = computed(() => !!files.value)

const chipLabel = computed(() => {
  const current = files.value
  if (current?.name) return current.name

  const lastUploaded = local.attachments.length > 0 ? local.attachments[local.attachments.length - 1] : ''
  return lastUploaded || ''
})

const isUploaded = computed(() => !files.value && local.attachments.length > 0)

// const emptyProgress: UploadProgressItem = { percent: 0, color: 'primary', icon: 'attach_file' }

function ensureProgress(index: number): UploadProgressItem {
  while (uploadProgress.value.length <= index) {
    uploadProgress.value.push({ percent: 0, color: 'primary', icon: 'attach_file' })
  }
  const item = uploadProgress.value[index]
  if (!item) {
    const fallback = { percent: 0, color: 'primary', icon: 'attach_file' } satisfies UploadProgressItem
    uploadProgress.value[index] = fallback
    return fallback
  }
  return item
}

function emitUpdated() {
  emit('update:modelValue', {
    comments: local.comments || '',
    attachments: [...local.attachments],
  })
}

function updateFiles(val: File | null) {
  if (val && !isAllowedFile(val)) {
    $q.notify({ type: 'negative', message: t('fileTypeNotAllowed', { allowed: allowedLabel }) })
    files.value = null
    isUploading.value = false
    uploadProgress.value = []
    return
  }

  files.value = val
  isUploading.value = false

  uploadProgress.value = val
    ? [{ percent: 0, color: 'primary', icon: 'attach_file' }]
    : []
}

function cancelFile(index: number) {
  const currentProgress = uploadProgress.value
  // single file selection
  files.value = null

  const nextProgress = [...currentProgress]
  nextProgress.splice(index, 1)
  uploadProgress.value = nextProgress
}

function cancelSelection() {
  cancelFile(0)
}

async function deleteLastUploadedAttachment() {
  const filename = local.attachments.length > 0 ? local.attachments[local.attachments.length - 1] : ''
  if (!filename) return

  const session = props.session
  const prop = props.id
  const id = userStore.id

  if (!session || !id) {
    local.attachments = []
    emitUpdated()
    return
  }

  try {
    await deleteAttachment({
      id,
      session,
      ...(prop ? { prop } : {}),
      filename,
    })
    local.attachments = []
    emitUpdated()
    $q.notify({ type: 'positive', message: t('fileDeleted') })
  } catch (e) {
    $q.notify({ type: 'negative', message: e instanceof Error ? e.message : t('deleteFailed') })
  }
}

function onChipRemove() {
  if (props.disable) return
  if (files.value) {
    cancelSelection()
    return
  }
  void deleteLastUploadedAttachment()
}

async function upload() {
  if (props.disable) return
  const file = files.value
  if (!file) return
  if (!isAllowedFile(file)) {
    $q.notify({ type: 'negative', message: t('fileTypeNotAllowed', { allowed: allowedLabel }) })
    return
  }

  const session = props.session
  const prop = props.id
  const user = userStore.id
  if (!session || !user) {
    $q.notify({ type: 'warning', message: t('missingUserOrSession') })
    return
  }

  isUploading.value = true

  try {
    const res = await uploadDocument({
      user,
      session,
      ...(prop ? { prop } : {}),
      file,
      onProgress: (fraction) => {
        ensureProgress(0).percent = fraction
      },
    })

    const p = ensureProgress(0)
    p.percent = 1
    p.color = 'positive'
    p.icon = 'check'

    const uploadedName = res.files?.[0] ?? file.name
    local.attachments.push(uploadedName)
    emitUpdated()
    $q.notify({ type: 'positive', message: t('fileUploaded') })
  } catch (e) {
    const p = ensureProgress(0)
    p.color = e instanceof ApiError && e.message.includes('cancel') ? 'warning' : 'negative'
    p.icon = e instanceof ApiError && e.message.includes('cancel') ? 'close' : 'error'
    const isCancel = e instanceof ApiError && e.message.includes('cancel')
    $q.notify({
      type: p.color === 'warning' ? 'warning' : 'negative',
      message: isCancel ? t('uploadCancelled') : (e instanceof Error ? e.message : t('uploadFailed')),
    })
  }

  // Clear selection after upload; keep uploaded names in local.attachments
  files.value = null
  uploadProgress.value = []
  isUploading.value = false
}

watch(
  () => props.modelValue,
  (v) => {
    if (v && typeof v.comments === 'string') {
      local.comments = v.comments
    } else if (v === null) {
      local.comments = ''
    }

    // modelValue is Partial<Payload>: don't wipe local attachments if the parent omits the field
    if (v && Array.isArray(v.attachments)) {
      local.attachments = [...v.attachments]
    } else if (v === null) {
      local.attachments = []
    }

    comments.value = local.comments
  },
  { deep: true }
)

function isStringArray(value: unknown): value is string[] {
  return Array.isArray(value) && value.every((x) => typeof x === 'string')
}

onMounted(async () => {
  if (props.autoload === false) return

  // session comes from component prop id; user id comes from userstore
  const session = props.session
  const id = userStore.id
  if (!session || !id) return

  try {
    const files = await loadAttachments({
      id,
      session,
      ...(props.id ? { prop: props.id } : {}),
    })
    if (isStringArray(files)) {
      local.attachments = [...files]
      emitUpdated()
    }
  } catch {
    // ignore network errors
  }
})
</script>

<style scoped>
.comment-attachment { font-size: 14px; }
.comment-attachment__file-chip { max-width: 320px; }
.text-grey { color: rgba(0,0,0,0.45); }
</style>
