<script setup lang="ts">
import { onMounted, reactive, watch, computed } from "vue";
import type { ComputedRef } from "vue";
import type {
  S3ObjectMetaInformation,
  BucketPermissionOut,
} from "@/client/s3proxy";
import type {
  FolderTree,
  S3PseudoFolder,
  S3ObjectWithFolder,
} from "@/types/PseudoFolder";
import { ObjectService } from "@/client/s3proxy";
import BootstrapIcon from "@/components/BootstrapIcon.vue";
import { filesize } from "filesize";
import dayjs from "dayjs";
import { Toast, Tooltip } from "bootstrap";
import PermissionListModal from "@/components/object-storage/modals/PermissionListModal.vue";
import UploadObjectModal from "@/components/object-storage/modals/UploadObjectModal.vue";
import CopyObjectModal from "@/components/object-storage/modals/CopyObjectModal.vue";
import PermissionModal from "@/components/object-storage/modals/PermissionModal.vue";
import ObjectDetailModal from "@/components/object-storage/modals/ObjectDetailModal.vue";
import CreateFolderModal from "@/components/object-storage/modals/CreateFolderModal.vue";
import DeleteModal from "@/components/modals/DeleteModal.vue";
import {
  S3Client,
  DeleteObjectCommand,
  DeleteObjectsCommand,
  GetObjectCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { awsAuthMiddlewareOptions } from "@aws-sdk/middleware-signing";
import { useAuthStore } from "@/stores/auth";
import { useBucketStore } from "@/stores/buckets";
import { environment } from "@/environment";

const authStore = useAuthStore();
const bucketRepository = useBucketStore();

const middleware = [
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  (next) => async (args) => {
    args.request.headers["host"] = environment.S3_URL.split("://")[1];
    return await next(args);
  },
  {
    relation: "before",
    toMiddleware: awsAuthMiddlewareOptions?.name ?? "impossible",
  },
];

let client = new S3Client({
  region: "us-east-1",
  endpoint: environment.S3_URL,
  forcePathStyle: true,
  credentials: {
    accessKeyId: authStore.s3key?.access_key ?? "",
    secretAccessKey: authStore.s3key?.secret_key ?? "",
  },
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
client.middlewareStack.addRelativeTo(middleware[0], middleware[1]);

// If S3 Key changes
authStore.$onAction(({ name, args }) => {
  if (name === "setS3Key") {
    if (args[0] === null) {
      console.error("There are no S3 Keys");
    } else {
      client = new S3Client({
        region: "us-east-1",
        endpoint: environment.S3_URL,
        forcePathStyle: true,
        credentials: {
          accessKeyId: args[0].access_key,
          secretAccessKey: args[0].secret_key,
        },
      });
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      client.middlewareStack.addRelativeTo(middleware[0], middleware[1]);
    }
  }
});

// Constants
// -----------------------------------------------------------------------------

const props = defineProps<{
  bucketName: string;
  subFolders: string[] | string;
  permission: BucketPermissionOut | undefined;
}>();
const randomIDSuffix = Math.random().toString(16).substr(2, 8);
let successToast: Toast | null = null;

// Reactive State
// -----------------------------------------------------------------------------

const deleteObjectsState = reactive({
  deletedItem: "",
  potentialObjectToDelete: "",
  deleteFolder: true,
} as {
  deletedItem: string;
  potentialObjectToDelete: string;
  deleteFolder: boolean;
});

const objectState = reactive({
  objects: [],
  loading: true,
  filterString: "",
  bucketNotFoundError: false,
  bucketPermissionError: false,
  createdPermission: undefined,
  editObjectKey: "",
  copyObject: {
    key: "",
    size: 0,
    bucket: "",
    last_modified: "2022-01-01",
    content_type: "text/plain",
  },
  viewDetailObject: {
    key: "",
    size: 0,
    bucket: "",
    last_modified: "2022-01-01",
    content_type: "text/plain",
  },
} as {
  objects: S3ObjectMetaInformation[];
  loading: boolean;
  filterString: string;
  bucketNotFoundError: boolean;
  bucketPermissionError: boolean;
  createdPermission: undefined | BucketPermissionOut;
  editObjectKey: string;
  copyObject: S3ObjectMetaInformation;
  viewDetailObject: S3ObjectMetaInformation;
});

// Watcher
// -----------------------------------------------------------------------------
watch(
  () => props.bucketName,
  (newBucketName, oldBucketName) => {
    if (oldBucketName !== newBucketName) {
      // If bucket is changed, update the objects
      updateObjects(newBucketName);
      objectState.filterString = "";
    }
  }
);

// Computed Properties
// -----------------------------------------------------------------------------
const filteredObjects: ComputedRef<(S3ObjectWithFolder | S3PseudoFolder)[]> =
  computed(() => {
    return objectState.filterString.length > 0
      ? visibleObjects.value.filter((obj) =>
          obj.key.includes(objectState.filterString)
        )
      : visibleObjects.value;
  });

const folderStructure: ComputedRef<FolderTree> = computed(() => {
  /**
   * Store the entire folder structure in a bucket in a tree-like data structure
   */
  return objectsWithFolders.value.reduce(
    // Add one object after another to the folder structure
    (fTree, currentObject) => {
      // If the object is not in a sub folder, but it in the top level 'folder'
      if (currentObject.folder.length === 0) {
        fTree.files.push(currentObject);
      } else {
        // If the object is in a sub folder
        let currentFolder: FolderTree = fTree;
        // For every sub folder the object is in , navigate into the sub folder
        for (const folderName of currentObject.folder) {
          // If the sub folder doesn't exist yet, create it
          if (
            Object.keys(currentFolder.subFolders).find(
              (subFolderName) => subFolderName === folderName
            ) == undefined
          ) {
            currentFolder.subFolders[folderName] = {
              subFolders: {},
              files: [],
            };
          }
          // navigate into the sub folder
          currentFolder = currentFolder.subFolders[folderName] as FolderTree;
        }
        // Add object to the folder
        currentFolder.files.push(currentObject);
      }
      return fTree;
    },
    // Empty folder structure as initial value
    {
      subFolders: {},
      files: [],
    } as FolderTree
  );
});

const objectsWithFolders: ComputedRef<S3ObjectWithFolder[]> = computed(() => {
  /**
   * Add to the meta information from objects the pseudo filename and their pseudo folder
   * This can be inferred from the key of the object where the '/' character is the delimiter, e.g.
   * dir1/dir2/text.txt ->
   *  folder: dir1, dir2
   *  filename: text.txt
   */
  return objectState.objects.map((obj) => {
    const splittedKey = obj.key.split("/");
    return {
      ...obj,
      pseudoFileName: splittedKey[splittedKey.length - 1],
      folder: splittedKey.slice(0, splittedKey.length - 1),
    };
  });
});

const currentSubFolders: ComputedRef<string[]> = computed(() => {
  /**
   * Transform a single sub folder from a string to an array containing the string and
   * replace an empty string with an empty list
   */
  return props.subFolders instanceof Array
    ? props.subFolders
    : props.subFolders.length > 0
    ? [props.subFolders]
    : [];
});

const visibleObjects: ComputedRef<(S3ObjectWithFolder | S3PseudoFolder)[]> =
  computed(() => {
    /**
     * Compute the visible objects based on the current sub folder
     */
    let currentFolder = folderStructure.value;
    // Navigate into right sub folder
    for (const subFolder of currentSubFolders.value) {
      if (currentFolder.subFolders[subFolder] == null) {
        // If sub folder doesn't exist, no object is visible
        return [];
      } else {
        currentFolder = currentFolder.subFolders[subFolder];
      }
    }
    // Add all objects and sub folders from the current sub folder as visible object
    const arr = [];
    arr.push(...currentFolder.files);
    arr.push(
      ...Object.keys(currentFolder.subFolders).map((subFolderName) => {
        const folderSize = calculateFolderSize(
          currentFolder.subFolders[subFolderName]
        );
        const folderLastModified = dayjs(
          calculateFolderLastModified(currentFolder.subFolders[subFolderName])
        ).toISOString();
        return {
          name: subFolderName,
          size: folderSize,
          key: subFolderName,
          parentFolder: currentSubFolders.value,
          last_modified: folderLastModified,
        } as S3PseudoFolder;
      })
    );
    return arr.filter((obj) => !obj.key.endsWith(".s3keep"));
  });

const subFolderInUrl: ComputedRef<boolean> = computed(
  () => currentSubFolders.value.length > 0
);
const errorLoadingObjects: ComputedRef<boolean> = computed(
  () => objectState.bucketPermissionError || objectState.bucketNotFoundError
);
const writableBucket: ComputedRef<boolean> = computed(() =>
  bucketRepository.writableBucket(props.bucketName)
);
const readableBucket: ComputedRef<boolean> = computed(() =>
  bucketRepository.readableBucket(props.bucketName)
);

// Lifecycle Hooks
// -----------------------------------------------------------------------------
onMounted(() => {
  updateObjects(props.bucketName);
  document
    .querySelectorAll(".tooltip-container")
    .forEach(
      (tooltipTriggerEl) => new Tooltip(tooltipTriggerEl, { trigger: "hover" })
    );
  successToast = new Toast("#successToast-" + randomIDSuffix);
});

// Functions
// -----------------------------------------------------------------------------
/**
 * Calculate recursively the cumulative file size of all o objects in a folder
 * @param folder Folder to inspect
 * @returns The size of this folder in bytes
 */
function calculateFolderSize(folder: FolderTree): number {
  let folderSize = 0;
  folderSize += folder.files.reduce((acc, file) => acc + file.size, 0);
  for (const subFolderName of Object.keys(folder.subFolders)) {
    folderSize += calculateFolderSize(folder.subFolders[subFolderName]);
  }
  return folderSize;
}

/**
 * Calculate recursively when an object in a folder were modified the last time
 * @param folder Folder to inspect
 * @returns The last modified timestamp as ISO string
 */
function calculateFolderLastModified(folder: FolderTree): string {
  let lastModified: dayjs.Dayjs;
  lastModified = folder.files
    .map((f) => dayjs(f.last_modified))
    .reduce(
      (acc, fileAccessed) => (fileAccessed.isAfter(acc) ? fileAccessed : acc),
      dayjs("2000-01-01")
    );
  for (const subFolderName of Object.keys(folder.subFolders)) {
    const lastModifiedSubFolder = dayjs(
      calculateFolderLastModified(folder.subFolders[subFolderName])
    );
    if (lastModifiedSubFolder.isAfter(lastModified)) {
      lastModified = lastModifiedSubFolder;
    }
  }
  return lastModified.toISOString();
}

/**
 * Load the meta information about objects from a bucket
 * @param bucketName Name of a bucket
 */
function updateObjects(bucketName: string) {
  objectState.bucketNotFoundError = false;
  objectState.bucketPermissionError = false;
  objectState.loading = true;
  ObjectService.objectGetBucketObjects(bucketName)
    .then((objs) => {
      objectState.objects = objs;
    })
    .catch((error) => {
      if (error.status === 404) {
        objectState.bucketNotFoundError = true;
      } else if (error.status == 403) {
        objectState.bucketPermissionError = true;
      }
    })
    .finally(() => {
      objectState.loading = false;
    });
}

function isS3Object(
  obj: S3PseudoFolder | S3ObjectWithFolder
): obj is S3ObjectWithFolder {
  return (obj as S3ObjectWithFolder).folder !== undefined;
}

/**
 * callback function when an object has been uploaded
 * @param newObject Uploaded object
 */
function objectUploaded(newObject: S3ObjectMetaInformation) {
  bucketRepository.fetchBucket(newObject.bucket);
  const index = objectState.objects.findIndex(
    (obj) => obj.key === newObject.key
  );
  if (index > -1) {
    objectState.objects[index] = newObject;
  } else {
    objectState.objects.push(newObject);
  }
}

/**
 * callback function when an object has been copied
 * @param copiedObject Uploaded object
 */
function objectCopied(copiedObject: S3ObjectMetaInformation) {
  bucketRepository.fetchBucket(copiedObject.bucket);
  if (copiedObject.bucket === props.bucketName) {
    objectState.objects.push(copiedObject);
  }
}

function deleteObject(key: string) {
  deleteObjectsState.potentialObjectToDelete = key;
  deleteObjectsState.deleteFolder = false;
}

/**
 * Delete an Object in the current folder
 * @param key Key of the Object
 */
function confirmedDeleteObject(key: string) {
  const command = new DeleteObjectCommand({
    Bucket: props.bucketName,
    Key: key,
  });
  client
    .send(command)
    .then(() => {
      bucketRepository.fetchBucket(props.bucketName);
      const splittedKey = key.split("/");
      deleteObjectsState.deletedItem = splittedKey[splittedKey.length - 1];
      successToast?.show();
      objectState.objects = objectState.objects.filter(
        (obj) => obj.key !== key
      );
    })
    .catch((err) => {
      console.error(err);
    });
}

/**
 * Initiate the download of the provided object
 * @param key Key of the object
 * @param bucket Bucket of the object
 */
async function downloadObject(key: string, bucket: string) {
  const command = new GetObjectCommand({
    Bucket: bucket,
    Key: key,
  });
  const url = await getSignedUrl(client, command, { expiresIn: 30 });
  //creating an invisible element
  const element = document.createElement("a");
  element.setAttribute("href", url);
  element.setAttribute("target", "_blank");
  document.body.appendChild(element);
  element.click();
  document.body.removeChild(element);
}

function deleteFolder(folderPath: string) {
  deleteObjectsState.potentialObjectToDelete = folderPath;
  deleteObjectsState.deleteFolder = true;
}

/**
 * Delete a folder in the current Bucket
 * @param folderPath Path to the folder with a trailing "/", e.g. some/path/to/a/folder/
 */
function confirmedDeleteFolder(folderPath: string) {
  const command = new DeleteObjectsCommand({
    Bucket: props.bucketName,
    Delete: {
      Objects: objectState.objects
        .filter((obj) => obj.key.startsWith(folderPath))
        .map((obj) => {
          return { Key: obj.key };
        }),
    },
  });
  client
    .send(command)
    .then(() => {
      bucketRepository.fetchBucket(props.bucketName);
      const splittedPath = folderPath.split("/");
      deleteObjectsState.deletedItem = splittedPath[splittedPath.length - 2];
      successToast?.show();
      objectState.objects = objectState.objects.filter(
        (obj) => !obj.key.startsWith(folderPath)
      );
    })
    .catch((err) => {
      console.error(err);
    });
}

function getObjectFileName(key: string): string {
  const splittedKey = key.split("/");
  return splittedKey[splittedKey.length - 1];
}

watch(
  visibleObjects,
  (visObjs) => {
    if (visObjs.length > 0) {
      // Initialise tooltips after DOM changes
      setTimeout(() => {
        document
          .querySelectorAll("span.date-tooltip")
          .forEach((tooltipTriggerEl) => new Tooltip(tooltipTriggerEl));
      }, 500);
    }
  },
  { flush: "post" }
);
</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 deleted {{ deleteObjectsState.deletedItem }}
        </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>
  <DeleteModal
    modalID="delete-object-modal"
    :object-name-delete="deleteObjectsState.potentialObjectToDelete"
    :back-modal-id="undefined"
    @confirm-delete="
      deleteObjectsState.deleteFolder
        ? confirmedDeleteFolder(deleteObjectsState.potentialObjectToDelete)
        : confirmedDeleteObject(deleteObjectsState.potentialObjectToDelete)
    "
  />
  <!-- Navbar Breadcrumb -->
  <nav aria-label="breadcrumb" class="fs-2">
    <ol class="breadcrumb">
      <li class="breadcrumb-item" :class="{ active: subFolderInUrl }">
        <router-link
          v-if="subFolderInUrl"
          :to="{
            name: 'bucket',
            params: { bucketName: props.bucketName, subFolders: [] },
          }"
          >{{ props.bucketName }}
        </router-link>
        <span v-else class="text-secondary">{{ props.bucketName }}</span>
      </li>
      <li
        class="breadcrumb-item"
        v-for="(folder, index) in currentSubFolders"
        :key="folder"
        :class="{ active: index === currentSubFolders.length }"
      >
        <router-link
          v-if="index !== currentSubFolders.length - 1"
          :to="{
            name: 'bucket',
            params: {
              bucketName: props.bucketName,
              subFolders: currentSubFolders.slice(0, index + 1),
            },
          }"
          >{{ folder }}
        </router-link>
        <span v-else class="text-secondary">{{ folder }}</span>
      </li>
    </ol>
  </nav>
  <!-- Inputs on top -->
  <!-- Search bucket text input -->
  <div class="row">
    <div class="col-8">
      <div class="input-group mt-2">
        <span class="input-group-text" id="objects-search-wrapping"
          ><bootstrap-icon icon="search" :width="16" :height="16"
        /></span>
        <input
          type="text"
          class="form-control"
          placeholder="Search Objects"
          aria-label="Search Objects"
          aria-describedby="objects-search-wrapping"
          :disabled="errorLoadingObjects"
          v-model.trim="objectState.filterString"
        />
      </div>
    </div>
    <!-- Upload object button -->
    <div id="BucketViewButtons" class="col-auto">
      <button
        type="button"
        class="btn btn-secondary me-2 tooltip-container"
        :disabled="errorLoadingObjects || !writableBucket"
        data-bs-toggle="modal"
        data-bs-title="Upload Object"
        data-bs-target="#upload-object-modal"
      >
        <bootstrap-icon icon="upload" :width="16" :height="16" fill="white" />
        <span class="visually-hidden">Upload Object</span>
      </button>
      <upload-object-modal
        :bucket-name="props.bucketName"
        :s3-client="client"
        modalID="upload-object-modal"
        :key-prefix="currentSubFolders.join('/')"
        :edit-object-file-name="undefined"
        @object-created="objectUploaded"
      />
      <!-- Add folder button -->
      <button
        type="button"
        class="btn btn-secondary m-2 tooltip-container"
        :disabled="errorLoadingObjects || !writableBucket"
        data-bs-toggle="modal"
        data-bs-title="Create Folder"
        data-bs-target="#create-folder-modal"
      >
        <bootstrap-icon icon="plus-lg" :width="16" :height="16" fill="white" />
        Folder
        <span class="visually-hidden">Add Folder</span>
      </button>
      <create-folder-modal
        :bucket-name="props.bucketName"
        :s3-client="client"
        modalID="create-folder-modal"
        :key-prefix="currentSubFolders.join('/')"
        @folder-created="objectUploaded"
      />
      <!-- Add bucket permission button -->
      <button
        v-if="!authStore.foreignUser"
        :hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)"
        type="button"
        class="btn btn-secondary m-2 tooltip-container"
        :disabled="errorLoadingObjects"
        data-bs-toggle="modal"
        data-bs-title="Create Bucket Permission"
        data-bs-target="#create-permission-modal"
      >
        <bootstrap-icon
          icon="person-plus-fill"
          :width="16"
          :height="16"
          fill="white"
        />
        <span class="visually-hidden">Add Bucket Permission</span>
      </button>
      <permission-modal
        v-if="!authStore.foreignUser"
        modalID="create-permission-modal"
        :bucket-name="props.bucketName"
        :sub-folders="folderStructure"
        :edit-user-permission="undefined"
        :editable="false"
        :readonly="false"
        :deletable="false"
        :back-modal-id="undefined"
        @permission-created="
          (newPermission) => (objectState.createdPermission = newPermission)
        "
      />
      <button
        v-if="!authStore.foreignUser"
        :hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)"
        type="button"
        class="btn btn-secondary m-2 tooltip-container"
        :disabled="errorLoadingObjects"
        data-bs-title="List Bucket Permission"
        data-bs-toggle="modal"
        data-bs-target="#permission-list-modal"
      >
        <bootstrap-icon
          icon="person-lines-fill"
          :width="16"
          :height="16"
          fill="white"
        />
        <span class="visually-hidden">View Bucket Permissions</span>
      </button>
      <permission-list-modal
        v-if="
          bucketRepository.getBucketPermission(props.bucketName) == null &&
          !authStore.foreignUser
        "
        :bucket-name="props.bucketName"
        :sub-folders="folderStructure"
        modalID="permission-list-modal"
        :add-permission="objectState.createdPermission"
      />
    </div>
  </div>
  <!-- Body -->
  <div class="pt-3">
    <!-- If bucket not found -->
    <div v-if="objectState.bucketNotFoundError" class="text-center fs-2 mt-5">
      <bootstrap-icon
        icon="search"
        class="mb-3"
        :width="64"
        :height="64"
        style="color: var(--bs-secondary)"
        fill="currentColor"
      />
      <p>
        Bucket <i>{{ props.bucketName }}</i> not found
      </p>
    </div>
    <!-- If no permission for bucket -->
    <div
      v-else-if="objectState.bucketPermissionError"
      class="text-center fs-2 mt-5"
    >
      <bootstrap-icon
        icon="folder-x"
        class="mb-3"
        :width="64"
        :height="64"
        style="color: var(--bs-secondary)"
        fill="currentColor"
      />
      <p>You don't have permission for this bucket</p>
    </div>
    <!-- Show content of bucket -->
    <div v-else>
      <!-- Table header -->
      <table
        class="table table-dark table-striped table-hover caption-top align-middle"
      >
        <caption>
          Displaying
          {{
            objectState.loading ? 0 : filteredObjects.length
          }}
          Objects
        </caption>
        <thead>
          <tr>
            <th scope="col">Name</th>
            <th scope="col">Last Accessed</th>
            <th scope="col">Size</th>
            <th scope="col"></th>
          </tr>
        </thead>
        <!-- Table body when loading the objects -->
        <tbody v-if="objectState.loading">
          <tr v-for="n in 5" :key="n" class="placeholder-glow">
            <th scope="row">
              <span class="placeholder w-100 bg-secondary"></span>
            </th>
            <td><span class="placeholder w-50 bg-secondary"></span></td>
            <td><span class="placeholder w-50 bg-secondary"></span></td>
            <td></td>
          </tr>
        </tbody>
        <!-- Table body when no objects are in the bucket -->
        <tbody v-else-if="filteredObjects.length === 0">
          <tr>
            <td colspan="4" class="text-center fst-italic fw-light">
              No objects to display
            </td>
          </tr>
        </tbody>
        <!-- Table body when showing objects -->
        <tbody v-else>
          <tr v-for="obj in filteredObjects" :key="obj.key">
            <th scope="row" class="text-truncate">
              <!-- Show file name if row is an object -->
              <div v-if="isS3Object(obj)">{{ obj.pseudoFileName }}</div>
              <!-- Show link to subfolder if row is a folder -->
              <div v-else>
                <router-link
                  class="text-decoration-none"
                  :to="{
                    name: 'bucket',
                    params: {
                      bucketName: props.bucketName,
                      subFolders: obj.parentFolder.concat(obj.name),
                    },
                  }"
                  >{{ obj.name }}
                </router-link>
              </div>
            </th>
            <td>
              <span
                class="date-tooltip"
                data-bs-toggle="tooltip"
                :data-bs-title="
                  dayjs(obj.last_modified).format('DD.MM.YYYY HH:mm:ss')
                "
                >{{ dayjs(obj.last_modified).fromNow() }}</span
              >
            </td>
            <td>{{ filesize(obj.size) }}</td>
            <!-- Show buttons with dropdown menu if row is an object -->
            <td class="text-end">
              <div
                v-if="isS3Object(obj)"
                class="btn-group btn-group-sm dropdown-center dropdown-menu-start"
              >
                <!-- Download Button -->
                <button
                  type="button"
                  class="btn btn-secondary"
                  @click="downloadObject(obj.key, props.bucketName)"
                  :disabled="!readableBucket"
                >
                  Download
                </button>
                <button
                  type="button"
                  class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
                  data-bs-toggle="dropdown"
                  aria-expanded="false"
                >
                  <span class="visually-hidden">Toggle Dropdown</span>
                </button>
                <!-- Dropdown menu -->
                <ul class="dropdown-menu dropdown-menu-dark">
                  <li>
                    <button
                      class="dropdown-item"
                      type="button"
                      data-bs-toggle="modal"
                      data-bs-target="#detail-object-modal"
                      @click="objectState.viewDetailObject = obj"
                    >
                      Details
                    </button>
                  </li>
                  <li>
                    <button
                      class="dropdown-item"
                      type="button"
                      :disabled="!writableBucket"
                      data-bs-toggle="modal"
                      data-bs-target="#edit-object-modal"
                      @click="objectState.editObjectKey = obj.key"
                    >
                      Edit
                    </button>
                  </li>
                  <li>
                    <button
                      class="dropdown-item"
                      type="button"
                      :disabled="!readableBucket"
                      data-bs-toggle="modal"
                      data-bs-target="#copy-object-modal"
                      @click="objectState.copyObject = obj"
                    >
                      Copy
                    </button>
                  </li>
                  <li>
                    <button
                      class="dropdown-item text-danger align-middle"
                      type="button"
                      @click="deleteObject(obj.key)"
                      data-bs-toggle="modal"
                      data-bs-target="#delete-object-modal"
                      :disabled="!writableBucket"
                    >
                      <bootstrap-icon
                        icon="trash-fill"
                        class="text-danger"
                        :width="13"
                        :height="13"
                        fill="currentColor"
                      />
                      <span class="ms-1">Delete</span>
                    </button>
                  </li>
                </ul>
              </div>
              <!-- Show delete button when row is a folder -->
              <div v-else>
                <button
                  type="button"
                  class="btn btn-danger btn-sm align-middle"
                  :disabled="!writableBucket"
                  data-bs-toggle="modal"
                  data-bs-target="#delete-object-modal"
                  @click="
                    deleteFolder(
                      obj.parentFolder.concat(['']).join('/') + obj.name + '/'
                    )
                  "
                >
                  <bootstrap-icon
                    icon="trash-fill"
                    class="text-danger me-2"
                    :width="12"
                    :height="12"
                    fill="white"
                  />
                  <span>Delete</span>
                </button>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
      <upload-object-modal
        :bucket-name="props.bucketName"
        :s3-client="client"
        modalID="edit-object-modal"
        :key-prefix="currentSubFolders.join('/')"
        :edit-object-file-name="getObjectFileName(objectState.editObjectKey)"
        @object-created="objectUploaded"
      />
      <copy-object-modal
        :source-object="objectState.copyObject"
        :s3-client="client"
        modalID="copy-object-modal"
        @object-copied="objectCopied"
      />
      <object-detail-modal
        :s3-object="objectState.viewDetailObject"
        modalID="detail-object-modal"
      />
    </div>
  </div>
</template>

<style scoped></style>