<script setup lang="ts">
import type { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import { computed, onMounted, reactive, watch } from "vue";
import type { ComputedRef } from "vue";
import type { S3ObjectMetaInformation } from "@/client/s3proxy";
import dayjs from "dayjs";
import { filesize } from "filesize";
import { Modal, Toast } from "bootstrap";

const props = defineProps<{
  modalID: string;
  bucketName: string;
  keyPrefix: string;
  s3Client: S3Client;
  editObjectFileName: string | undefined;
}>();

const randomIDSuffix = Math.random().toString(16).substr(2, 8);
let uploadModal: Modal | null = null;
let successToast: Toast | null = null;
let errorToast: Toast | null = null;

const currentFolders: ComputedRef<string[]> = computed(() =>
  props.keyPrefix.split("/")
);

const emit = defineEmits<{
  (e: "object-created", object: S3ObjectMetaInformation): void;
}>();

watch(
  () => props.editObjectFileName,
  (nextFileName) => {
    formState.key = nextFileName ?? "";
  }
);

const formState = reactive({
  file: {},
  key: "",
  uploading: false,
  uploadDone: 0,
  uploadTotal: 1,
} as {
  file: File;
  key: string;
  uploading: boolean;
  uploadDone: number;
  uploadTotal: number;
});

const uploadProgress: ComputedRef<number> = computed(() =>
  Math.round((100 * formState.uploadDone) / formState.uploadTotal)
);

const editObject: ComputedRef<boolean> = computed(
  () => props.editObjectFileName !== undefined
);

async function uploadObject() {
  const key =
    props.keyPrefix.length > 0
      ? props.keyPrefix + "/" + formState.key
      : formState.key;
  try {
    formState.uploadDone = 0;
    formState.uploading = true;
    const parallelUploads3 = new Upload({
      client: props.s3Client,
      params: {
        Bucket: props.bucketName,
        Body: formState.file,
        ContentType: formState.file.type,
        Key: key,
      },
      queueSize: 4, // optional concurrency configuration
      partSize: 1024 * 1024 * 5, // optional size of each part, in bytes, at least 5MB
      leavePartsOnError: false, // optional manually handle dropped parts
    });

    parallelUploads3.on("httpUploadProgress", (progress) => {
      if (progress.loaded != null && progress.total != null) {
        formState.uploadDone = progress.loaded;
        formState.uploadTotal = progress.total;
      }
    });
    await parallelUploads3.done();
    uploadModal?.hide();
    successToast?.show();
    emit("object-created", {
      key: key,
      bucket: props.bucketName,
      size: formState.file?.size ?? 0,
      last_modified: dayjs().toISOString(),
      content_type: formState.file?.type ?? "text/plain",
    });
    formState.key = "";
    (
      document.getElementById("objectFile" + randomIDSuffix) as HTMLInputElement
    ).value = "";
  } catch (e) {
    console.error(e);
    errorToast?.show();
  } finally {
    formState.uploading = false;
  }
}

// eslint-disable-next-line
function fileChange(event: any) {
  formState.file = event.target.files[0];
  if (!editObject.value) {
    formState.key = formState.file.name;
  }
}

onMounted(() => {
  uploadModal = new Modal("#" + props.modalID);
  successToast = new Toast("#successToast-" + randomIDSuffix);
  errorToast = new Toast("#errorToast-" + randomIDSuffix);
});
</script>

<template>
  <div class="toast-container position-fixed top-toast end-0 p-3">
    <div
      role="alert"
      aria-live="assertive"
      aria-atomic="true"
      class="toast text-bg-success align-items-center border-0"
      data-bs-autohide="true"
      :id="'successToast-' + randomIDSuffix"
    >
      <div class="d-flex">
        <div class="toast-body">Successfully uploaded file</div>
        <button
          type="button"
          class="btn-close btn-close-white me-2 m-auto"
          data-bs-dismiss="toast"
          aria-label="Close"
        ></button>
      </div>
    </div>
  </div>
  <div class="toast-container position-fixed top-toast end-0 p-3">
    <div
      role="alert"
      aria-live="assertive"
      aria-atomic="true"
      class="toast text-bg-danger align-items-center border-0"
      data-bs-autohide="true"
      :id="'errorToast-' + randomIDSuffix"
    >
      <div class="d-flex">
        <div class="toast-body">
          There has been some Error.<br />
          Try again later
        </div>
        <button
          type="button"
          class="btn-close btn-close-white me-2 m-auto"
          data-bs-dismiss="toast"
          aria-label="Close"
        ></button>
      </div>
    </div>
  </div>
  <bootstrap-modal
    :modalID="modalID"
    :static-backdrop="true"
    modal-label="Upload Object Modal"
  >
    <template v-slot:header>
      <h4>Upload file to</h4>
      <ol class="breadcrumb">
        <li class="breadcrumb-item">{{ props.bucketName }}</li>
        <li
          class="breadcrumb-item"
          v-for="folder in currentFolders"
          :key="folder"
        >
          {{ folder }}
        </li>
      </ol>
    </template>
    <template v-slot:body>
      <div class="container-fluid">
        <div class="row">
          <form
            class="col-7"
            :id="'uploadObjectForm' + randomIDSuffix"
            @submit.prevent="uploadObject"
          >
            <div class="mb-3">
              <label
                :for="'objectFile' + randomIDSuffix"
                class="form-label"
                v-if="editObject"
              >
                New File Content *
              </label>
              <label
                :for="'objectFile' + randomIDSuffix"
                class="form-label"
                v-else
              >
                File *
              </label>
              <input
                class="form-control"
                type="file"
                :id="'objectFile' + randomIDSuffix"
                required
                @change="fileChange"
              />
            </div>
            <div class="mb-3">
              <label :for="'objectKey' + randomIDSuffix" class="form-label"
                >Filename</label
              >
              <input
                type="text"
                :class="{
                  'form-control-plaintext': editObject,
                  'form-control': !editObject,
                }"
                :id="'objectKey' + randomIDSuffix"
                required
                :disabled="editObject"
                v-model="formState.key"
              />
            </div>
          </form>
          <div class="col-5">
            Note: Delimiters ('/') are allowed in the file name to place the new
            file into a folder that will be created when the file is uploaded
            (to any depth of folders).
          </div>
        </div>
      </div>
    </template>
    <template v-slot:footer>
      <div class="w-50 me-auto" v-if="formState.uploading">
        <div class="progress">
          <div
            class="progress-bar bg-info"
            role="progressbar"
            aria-label="Basic example"
            :style="{ width: uploadProgress + '%' }"
            :aria-valuenow="uploadProgress"
            aria-valuemin="0"
            aria-valuemax="100"
          >
            {{ uploadProgress }}%
          </div>
        </div>
        <span v-if="formState.uploadDone > 0">
          {{ filesize(formState.uploadDone) }} /
          {{ filesize(formState.uploadTotal) }}
        </span>
      </div>
      <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
        Close
      </button>
      <button
        :disabled="formState.uploading"
        type="submit"
        :form="'uploadObjectForm' + randomIDSuffix"
        class="btn btn-primary"
      >
        <span
          v-if="formState.uploading"
          class="spinner-border spinner-border-sm"
          role="status"
          aria-hidden="true"
        ></span>
        Upload
      </button>
    </template>
  </bootstrap-modal>
</template>

<style scoped></style>