<script setup lang="ts">
import { onMounted, ref, useTemplateRef, watch, type Ref } from 'vue';
import AppButton from './AppButton.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 fileInput = useTemplateRef('fileInput')

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

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

onMounted(() => {
  files.value = props.initialFiles.map(f => new ExistingFileListItem(f))
  // If input initial files change, reset the file selector to just those.
  watch(() => props.initialFiles, () => {
    files.value = props.initialFiles.map(f => new ExistingFileListItem(f))
  })
  // When our internal model changes, update the defined uploaded/removed files models.
  watch(() => files, () => {
    // 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)
  }, { deep: true })
})

function onFileInputChanged(e: Event) {
  if (props.disabled) return
  const inputElement = e.target as HTMLInputElement
  const fileList = inputElement.files
  if (fileList === null) {
    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 = ''
}

function onFileDeleteClicked(idx: number) {
  if (props.disabled) return
  if (idx === 0 && files.value.length === 1) {
    files.value = []
    return
  }
  files.value.splice(idx, 1)
}
</script>
<template>
  <div class="file-selector">
    <div @click.prevent="">
      <div v-for="file, idx in files" :key="idx" class="file-selector-item">
        <div style="display: flex; align-items: center; margin-left: 1rem;">
          <div>
            <div>{{ file.filename }}</div>
            <div style="font-size: 0.75rem;">{{ file.contentType }}</div>
          </div>
        </div>
        <div>
          <AppButton v-if="!disabled" icon="trash" button-type="button" @click="onFileDeleteClicked(idx)" />
        </div>
      </div>
    </div>

    <div>
      <input id="fileInput" type="file" multiple @change="onFileInputChanged" style="display: none;" ref="fileInput"
        :disabled="disabled" />
      <label for="fileInput">
        <AppButton icon="upload" button-type="button" @click="fileInput?.click()" :disabled="disabled">Select a File
        </AppButton>
      </label>
    </div>
  </div>
</template>
<style lang="css">
.file-selector {
  width: 100%;
}

.file-selector-item {
  background-color: var(--bg-secondary);
  display: flex;
  justify-content: space-between;
  padding: 0.25rem;
  border-radius: 0.5rem;
  margin: 0.5rem 0;
}
</style>
