<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>
