<script setup lang="ts">
import { onMounted, ref, shallowRef, useTemplateRef, watch, type Ref } from 'vue'
import AppButton from '@/components/common/AppButton.vue'
import AttachmentRow from './AttachmentRow.vue'

interface ExistingFile {
  id: number
  filename: string
  contentType: string
  size: number
}

abstract class FileListItem {
  constructor(
    public readonly filename: string,
    public readonly contentType: string,
    public readonly size: number,
  ) { }
}

class ExistingFileListItem extends FileListItem {
  public readonly id: number
  constructor(file: ExistingFile) {
    super(file.filename, file.contentType, file.size)
    this.id = file.id
  }
}

class NewFileListItem extends FileListItem {
  public readonly file: File
  constructor(file: File) {
    super(file.name, file.type, file.size)
    this.file = file
  }
}

interface Props {
  disabled?: boolean
  initialFiles?: ExistingFile[]
}

const props = withDefaults(defineProps<Props>(), {
  disabled: false,
  initialFiles: () => [],
})
const previousInitialFiles: Ref<string> = ref('{}')
const fileInput = useTemplateRef('fileInput')

const uploadedFiles = defineModel<File[]>('uploaded-files', { default: [] })
const removedFiles = defineModel<number[]>('removed-files', { default: [] })

// Internal file list model:
const files = shallowRef<FileListItem[]>([])

onMounted(() => {
  files.value = props.initialFiles.map((f) => new ExistingFileListItem(f))
  previousInitialFiles.value = JSON.stringify(props.initialFiles)
  // If input initial files change, reset the file selector to just those.
  watch(
    () => props.initialFiles,
    () => {
      const prevJson = previousInitialFiles.value
      const newJson = JSON.stringify(props.initialFiles)
      if (prevJson !== newJson) {
        files.value = props.initialFiles.map((f) => new ExistingFileListItem(f))
        previousInitialFiles.value = newJson
      }
    },
  )
})

function syncModelsWithInternalFileList() {
  // Compute the set of uploaded files as just any newly uploaded file list item.
  uploadedFiles.value = files.value
    .filter((f) => f instanceof NewFileListItem)
    .map((f) => f.file)
  // Compute the set of removed files as those from the set of initial files whose ID is no longer present.
  const retainedExistingFileIds = files.value
    .filter((f) => f instanceof ExistingFileListItem)
    .map((f) => f.id)
  removedFiles.value = props.initialFiles
    .filter((f) => !retainedExistingFileIds.includes(f.id))
    .map((f) => f.id)
}

function onFileInputChanged(e: Event) {
  if (props.disabled) return
  const inputElement = e.target as HTMLInputElement
  const fileList = inputElement.files
  if (fileList === null) {
    files.value = []
    syncModelsWithInternalFileList()
    return
  }
  for (let i = 0; i < fileList?.length; i++) {
    const file = fileList.item(i)
    if (file !== null) {
      files.value = [...files.value, new NewFileListItem(file)]
    }
  }
  // Reset the input element after we've consumed the selected files.
  inputElement.value = ''
  syncModelsWithInternalFileList()
}

function onFileDeleteClicked(idx: number) {
  if (props.disabled) return
  if (idx === 0 && files.value.length === 1) {
    files.value = []
  } else {
    files.value.splice(idx, 1)
  }
  syncModelsWithInternalFileList()
}
</script>
<template>
  <div class="file-selector">
    <div @click.prevent="">
      <AttachmentRow v-for="(file, idx) in files" :key="idx" :attachment="file" @deleted="onFileDeleteClicked(idx)" />
    </div>

    <div>
      <input id="fileInput" type="file" capture="environment" accept="image/*,text/*,.pdf,.doc,.odt,.docx,.xlsx"
        multiple @change="onFileInputChanged" style="display: none" ref="fileInput" :disabled="disabled" />
      <label for="fileInput">
        <AppButton icon="upload" type="button" @click="fileInput?.click()" :disabled="disabled">Select a File
        </AppButton>
      </label>
    </div>
  </div>
</template>
<style lang="css">
.file-selector {
  width: 100%;
}
</style>
