<template>
  <div
    class="directory-view"
    :class="{ 'drop-target-active': dropTargetActive }"
    tabindex="0"
    ref="main"
    @keydown.up.prevent="onKeyUp($event)"
    @keydown.down.prevent="onKeyDown($event)"
    @keydown.left.prevent="onKeyLeft($event)"
    @keydown.right.prevent="onKeyRight($event)"
    @keydown.enter="onKeyEnter($event)"
    @keydown.delete="onKeyDelete($event)"
    @keydown.esc="onKeyEscape($event)"
    @drop.stop.prevent="onDrop(null, $event)"
    @dragover.stop.prevent="onDragOver($event)"
    @dragenter.prevent
    @dragexit="onDragExit($event)"
    @keydown="onKeyEvent($event)"
    >
    <Menu ref="contextMenu" :model="contextMenuModel" />
    <Menu ref="contextMenuBody" :model="contextMenuBodyModel" />

    <Dialog ref="chownDialog" :title="tr('filemanager.chownDialog.title')" :confirmLabel="tr('filemanager.chownDialog.change')" @confirm="onChangeOwnerDialogSubmit(changeOwnerDialog.items, changeOwnerDialog.ownerId)">
      <SingleSelect v-model="changeOwnerDialog.ownerId" :options="ownersModel" optionLabel="label" optionKey="uid"/>
    </Dialog>

    <div class="directory-view-empty-container" v-show="items.length === 0 && !busy" @contextmenu="onContextMenuBody($event)">
      <slot name="empty">
        Folder is empty
      </slot>
    </div>

    <div class="directory-view-header" v-if="items.length && viewMode === 'list'">
      <div class="directory-view-header-icon"></div>
      <div class="directory-view-header-name" @click="toggleSort(SORT_BY.NAME)"><i v-show="sortBy === SORT_BY.NAME" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.name') }}</div>
      <div class="directory-view-header-star" v-show="showStar" @click="toggleSort(SORT_BY.STAR)"><i v-show="sortBy === SORT_BY.STAR" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> <i class="fa-regular fa-star"></i></div>
      <div class="directory-view-header-owner" v-show="showOwner" @click="toggleSort(SORT_BY.OWNER)"><i v-show="sortBy === SORT_BY.OWNER" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.owner') }}</div>
      <div class="directory-view-header-size" v-show="showSize" @click="toggleSort(SORT_BY.SIZE)"><i v-show="sortBy === SORT_BY.SIZE" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.size') }}</div>
      <div class="directory-view-header-modified" v-show="showModified" @click="toggleSort(SORT_BY.MTIME)"><i v-show="sortBy === SORT_BY.MTIME" class="fa-solid" :class="{ 'fa-arrow-up': sortDirection === SORT_DIRECTION.DESC, 'fa-arrow-down': sortDirection === SORT_DIRECTION.ASC }"></i> {{ tr('filemanager.list.mtime') }}</div>
      <div class="directory-view-header-actions"></div>
    </div>
    <div class="directory-view-body-container" :class="{ 'directory-view-body-container-full': viewMode !== 'list' }" v-show="items.length">
      <div
        ref="gridBody"
        class="directory-view-body"
        :class="{ 'directory-view-body-grid': viewMode === 'grid' }"
        @contextmenu="onContextMenuBody($event)"
        >
        <template v-if="viewMode === 'list'">
          <DirectoryViewListItem v-for="item in filteredSortedItems" :ref="(el) => setItemRef(item.id, el)"
            :key="item.id"
            :fallbackIcon="fallbackIcon"
            :item="item"
            :show-owner="showOwner"
            :show-extract="showExtract"
            :show-size="showSize"
            :show-star="showStar"
            :show-modified="showModified"
            :rename-handler="renameHandler"
            :share-indicator-property="shareIndicatorProperty"
            :select-handler="onSelectAndFocus"
            :drop-handler="onDrop"
            :can-drop-handler="onCanDropHandler"
            :star-handler="starHandler"
            @contextmenu="onContextMenu(item, $event)"
            @action-menu="onContextMenu"
            @dblclick="onItemActivated(item)"
            @activated="onItemActivated(item)"
            @dragstart="onItemDragStart($event, item)"
          />
        </template>
        <template v-else-if="viewMode === 'grid'">
          <DirectoryViewGridItem v-for="item in filteredSortedItems" :ref="(el) => setItemRef(item.id, el)"
            :key="item.id"
            :fallbackIcon="fallbackIcon"
            :item="item"
            :show-star="showStar"
            :rename-handler="renameHandler"
            :share-indicator-property="shareIndicatorProperty"
            :select-handler="onSelectAndFocus"
            :drop-handler="onDrop"
            :can-drop-handler="onCanDropHandler"
            :star-handler="starHandler"
            @contextmenu="onContextMenu(item, $event)"
            @action-menu="onContextMenu"
            @dblclick="onItemActivated(item)"
            @activated="onItemActivated(item)"
            @dragstart="onItemDragStart($event, item)"
          />
        </template>
      </div>

      <div class="drag-handle"></div>
    </div>
  </div>
</template>

<script setup>

import { ref, reactive, computed, useTemplateRef } from 'vue';

import SingleSelect from './SingleSelect.vue';
import DirectoryViewListItem from './DirectoryViewListItem.vue';
import DirectoryViewGridItem from './DirectoryViewGridItem.vue';
import Dialog from './Dialog.vue';
import Menu from './Menu.vue';

import { translation } from '../utils.js';

const SORT_BY = {
  NAME: 'name',
  OWNER: 'owner',
  STAR: 'star',
  SIZE: 'size',
  MTIME: 'mtime',
};

const SORT_DIRECTION = {
  ASC: 'asc',
  DESC: 'desc',
};

let rawSelected = [];

const props = defineProps({
  viewMode: {
    type: String,
    default: 'list',
    validator(value) {
      return ['list', 'grid'].includes(value);
    },
  },
  busy: {
    type: Boolean,
    default: false,
  },
  showOwner: {
    type: Boolean,
    default: false,
  },
  showSize: {
    type: Boolean,
    default: false,
  },
  showStar: {
    type: Boolean,
    default: false,
  },
  showExtract: {
    type: Boolean,
    default: true,
  },
  showNewFile: {
    type: Boolean,
    default: true,
  },
  showNewFolder: {
    type: Boolean,
    default: true,
  },
  showCut: {
    type: Boolean,
    default: true,
  },
  showCopy: {
    type: Boolean,
    default: true,
  },
  showPaste: {
    type: Boolean,
    default: true,
  },
  showRename: {
    type: Boolean,
    default: true,
  },
  showSelectAll: {
    type: Boolean,
    default: true,
  },
  showDownload: {
    type: Boolean,
    default: true,
  },
  showDelete: {
    type: Boolean,
    default: true,
  },
  showShare: {
    type: Boolean,
    default: false,
  },
  showModified: {
    type: Boolean,
    default: false,
  },
  shareIndicatorProperty: {
    type: String,
    default: '',
  },
  editable: {
    type: Boolean,
    default: true,
  },
  multiDownload: {
    type: Boolean,
    default: false,
  },
  tr: {
    type: Function,
    default(id) {
      console.warn('Missing tr for DirectoryView, using fallback.');
      return translation(id);
    },
  },
  items: {
    type: Array,
    validator(value) {
      function fail(prop, type, item) {
        console.error(`DirectoryView.items[].${prop} must be a ${type}`, item);
        return false;
      }

      for (const item of value) {
        if (typeof item.id !== 'string') return fail('id', 'string', item);
        if (typeof item.name !== 'string') return fail('name', 'string', item);
        if (typeof item.icon !== 'string') return fail('icon', 'string', item);
        if (typeof item.previewUrl !== 'string') return fail('previewUrl', 'string', item);

        if (item.href && typeof item.href !== 'string') return fail('href', 'string', item);
        if (item.target && typeof item.target !== 'string') return fail('target', 'string', item);
        if (item.owner && typeof item.owner !== 'string') return fail('owner', 'string', item);
        if (item.size && typeof item.size !== 'number') return fail('size', 'number', item);
        if (item.star && typeof item.star !== 'boolean') return fail('star', 'boolean', item);
      }

      return true;
    },
  },
  ownersModel: {
    type: Array,
    default: () => [],
  },
  fallbackIcon: String,
  showUploadFile: {
    type: Boolean,
    default: false,
  },
  showUploadFolder: {
    type: Boolean,
    default: false,
  },
  deleteHandler: {
    type: Function,
    default() {
      console.warn('Missing deleteHandler for DirectoryView');
    },
  },
  renameHandler: {
    type: Function,
    default() {
      console.warn('Missing renameHandler for DirectoryView');
    },
  },
  changeOwnerHandler: {
    type: Function,
    default() {
      console.warn('Missing changeOwnerHandler for DirectoryView');
    },
  },
  pasteHandler: {
    type: Function,
    default() {
      console.warn('Missing pasteHandler for DirectoryView');
    },
  },
  newFileHandler: {
    type: Function,
    default() {
      console.warn('Missing newFileHandler for DirectoryView');
    },
  },
  newFolderHandler: {
    type: Function,
    default() {
      console.warn('Missing newFolderHandler for DirectoryView');
    },
  },
  uploadFileHandler: {
    type: Function,
    default() {
      console.warn('Missing uploadFileHandler for DirectoryView');
    },
  },
  uploadFolderHandler: {
    type: Function,
    default() {
      console.warn('Missing uploadFolderHandler for DirectoryView');
    },
  },
  shareHandler: {
    type: Function,
    default() {
      console.warn('Missing shareHandler for DirectoryView');
    },
  },
  starHandler: {
    type: Function,
    default() {
      console.warn('Missing starHandler for DirectoryView');
    },
  },
  dropHandler: {
    type: Function,
    default() {
      console.warn('Missing dropHandler for DirectoryView');
    },
  },
  downloadHandler: {
    type: Function,
    default() {
      console.warn('Missing downloadHandler for DirectoryView');
    },
  },
  extractHandler: {
    type: Function,
    default() {
      console.warn('Missing extractHandler for DirectoryView');
    },
  },
  refreshHandler: {
    type: Function,
    default: null,
  },
});

const emit = defineEmits(['selectionChanged', 'item-activated']);

const gridBody = useTemplateRef('gridBody');
const contextMenu = useTemplateRef('contextMenu');
const contextMenuBody = useTemplateRef('contextMenuBody');
const chownDialog = useTemplateRef('chownDialog');

const _alreadyActivating = ref(false);
const elements = reactive({});
const focusItem = ref(null);
const focusItemIndex = ref(-1);
const selectedCount = ref(0);
const dropTargetActive = ref(false);
const sortBy = ref(SORT_BY.NAME);
const sortDirection = ref(SORT_DIRECTION.ASC);
const clipboard = reactive({
  action: '',
  files: [],
});
const changeOwnerDialog = reactive({
  busy: false,
  ownerId: '',
  items: [],
});

function setItemRef(id, el) {
  if (el) {
    elements[id] = el;
  } else {
    delete elements[id];
  }
}

function scrollItemIntoView(componentRef) {
  componentRef?.$el?.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' });
}

function getSelected() {
  return rawSelected;
}

function invalidateSelected() {
  rawSelected = props.items.filter((item) => item.selected).slice();
  selectedCount.value = rawSelected.length;
  emit('selectionChanged', rawSelected);
}

function highlight(item) {
  const id = item.id;
  if (!elements[id]) return;

  elements[id].highlight();
  scrollItemIntoView(elements[id]);
}

function highlightByName(name) {
  const tmp = props.items.find((i) => i.name === name);
  if (!tmp) return;

  highlight(tmp);
}

function toggleSort(by) {
  if (sortBy.value === by) {
    sortDirection.value = sortDirection.value === SORT_DIRECTION.ASC ? SORT_DIRECTION.DESC : SORT_DIRECTION.ASC;
  } else {
    sortBy.value = by;
  }
}

function onDrop(targetItem, event) {
  dropTargetActive.value = false;

  const targetItemName = targetItem ? targetItem.name : '';

  if (event.dataTransfer.getData('application/x-pankow') === 'selected') {
    if (!targetItemName) return;

    props.dropHandler(targetItemName, null, getSelected());
  } else if (event.dataTransfer && event.dataTransfer.items[0]) {
    props.dropHandler(targetItemName, event.dataTransfer, null);
  }
}

function onCanDropHandler(item) {
  if (!item.isDirectory) return false;
  return !item.selected;
}

function onDragOver(event) {
  if (!event.dataTransfer) return;

  if (event.dataTransfer.getData('application/x-pankow') === 'selected') {
    dropTargetActive.value = false;
  } else {
    event.dataTransfer.dropEffect = 'copy';
    dropTargetActive.value = true;
  }
}

function onDragExit() {
  dropTargetActive.value = false;
}

function onItemDragStart(event, item) {
  if (item && !item.selected) {
    onSelectAndFocus(item, {}, false);
  }

  const dragHandle = document.getElementsByClassName('drag-handle')[0];

  dragHandle.replaceChildren();

  for (let i = 0; i < Math.min(selectedCount.value, 6); ++i) {
    const dragHandleItem = document.createElement('div');
    dragHandleItem.style.opacity = 1 - ((i * 2) / 10);
    const dragHandleItemIcon = document.createElement('img');
    dragHandleItemIcon.src = rawSelected[i].previewUrl || rawSelected[i].icon;
    dragHandleItem.append(dragHandleItemIcon, rawSelected[i].name);
    dragHandle.append(dragHandleItem);
  }

  event.dataTransfer.dropEffect = 'move';
  event.dataTransfer.setData('application/x-pankow', 'selected');
  event.dataTransfer.setDragImage(dragHandle, 0, 0);
}

function getGridColumns() {
  const el = gridBody.value;
  if (props.viewMode !== 'grid' || !el || !el.children.length) return 1;
  const firstRowTop = el.children[0].getBoundingClientRect().top;
  const tolerance = 2;
  let cols = 0;
  for (const child of el.children) {
    if (Math.abs(child.getBoundingClientRect().top - firstRowTop) <= tolerance) cols++;
    else break;
  }
  return cols || 1;
}

function onKeyUp(event) {
  const step = props.viewMode === 'grid' ? getGridColumns() : 1;
  onSelectAndFocusIndex(focusItemIndex.value - step, event.shiftKey);
}

function onKeyDown(event) {
  const step = props.viewMode === 'grid' ? getGridColumns() : 1;
  onSelectAndFocusIndex(focusItemIndex.value + step, event.shiftKey);
}

function onKeyLeft(event) {
  if (props.viewMode !== 'grid') return;
  onSelectAndFocusIndex(focusItemIndex.value - 1, event.shiftKey);
}

function onKeyRight(event) {
  if (props.viewMode !== 'grid') return;
  onSelectAndFocusIndex(focusItemIndex.value + 1, event.shiftKey);
}

function onKeyEnter() {
  if (focusItem.value) onItemActivated(focusItem.value);
}

async function onKeyDelete(event) {
  if (event.key !== 'Delete') return;
  if (!focusItem.value) return;

  await props.deleteHandler(getSelected());
}

function onKeyEscape() {
  onUnselectAll();
}

function onKeyEvent(event) {
  if (event.key === 'c' && (event.ctrlKey || event.metaKey)) {
    onItemsCopy();
    event.preventDefault();
  } else if (event.key === 'v' && (event.ctrlKey || event.metaKey)) {
    props.pasteHandler(clipboard.action, clipboard.files);
    event.preventDefault();
  } else if (event.key === 'x' && (event.ctrlKey || event.metaKey)) {
    onItemsCut();
    event.preventDefault();
  } else if (event.key === 'a' && (event.ctrlKey || event.metaKey)) {
    onSelectAll();
    event.preventDefault();
  } else if (event.key === 'End') {
    if (event.shiftKey) onSelectAndFocusToIndex(props.items.length - 1);
    else onSelectAndFocusIndex(props.items.length - 1, event.shiftKey);
    event.preventDefault();
  } else if (event.key === 'Home') {
    if (event.shiftKey) onSelectAndFocusToIndex(0);
    else onSelectAndFocusIndex(0, event.shiftKey);
    event.preventDefault();
  } else if (event.key === 'PageUp') {
    const pageItemCount = parseInt(event.target.clientHeight / 55, 10);
    if (event.shiftKey) onSelectAndFocusToIndex(focusItemIndex.value - pageItemCount);
    else onSelectAndFocusIndex(focusItemIndex.value - pageItemCount, event.shiftKey);
    event.preventDefault();
  } else if (event.key === 'PageDown') {
    const pageItemCount = parseInt(event.target.clientHeight / 55, 10);
    if (event.shiftKey) onSelectAndFocusToIndex(focusItemIndex.value + pageItemCount);
    else onSelectAndFocusIndex(focusItemIndex.value + pageItemCount, event.shiftKey);
    event.preventDefault();
  }
}

function onContextMenu(item, event) {
  contextMenu.value?.open(event);
  onSelectAndFocus(item, event, false);
  event.stopPropagation();
}

function onContextMenuBody(event) {
  contextMenuBody.value?.open(event);
  onUnselectAll();
}

function onItemActivated(item) {
  if (_alreadyActivating.value) return;
  _alreadyActivating.value = true;
  setTimeout(() => {
    _alreadyActivating.value = false;
  }, 500);

  emit('item-activated', item);
}

function onItemsChangeOwner() {
  if (!focusItem.value) return console.warn('onItemsChangeOwner should only be triggered if at least one item is selected');

  changeOwnerDialog.items = getSelected();
  changeOwnerDialog.ownerId = focusItem.value.uid;
  chownDialog.value?.open();
}

function onItemDownload() {
  if (!focusItem.value) return console.warn('onItemDownload should only be triggered if at lesst one item is selected');

  props.downloadHandler(props.multiDownload ? getSelected() : focusItem.value);
}

function onItemExtract() {
  if (selectedCount.value !== 1) return console.warn('onItemExtract should only be triggered if one item is selected');

  props.extractHandler(focusItem.value);
}

function onItemsCopy() {
  if (!focusItem.value) return console.warn('onItemsCopy should only be triggered if at least one item is selected');

  clipboard.action = 'copy';
  clipboard.files = getSelected();
}

function onItemsCut() {
  if (!focusItem.value) return console.warn('onItemsCut should only be triggered if at least one item is selected');

  clipboard.action = 'cut';
  clipboard.files = getSelected();
}

function onItemShare() {
  if (selectedCount.value !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');

  props.shareHandler(focusItem.value);
}

function onItemRenameBegin() {
  if (selectedCount.value !== 1) return console.warn('onItemRenameBegin should only be triggered if one item is selected');

  elements[focusItem.value.id]?.onRenameBegin();
}

async function onChangeOwnerDialogSubmit(items, newOwnerUid) {
  changeOwnerDialog.busy = true;
  await props.changeOwnerHandler(items, newOwnerUid);
  changeOwnerDialog.visible = false;
  changeOwnerDialog.items = [];
  changeOwnerDialog.ownerId = props.ownersModel[0].uid;
  changeOwnerDialog.busy = false;
  chownDialog.value?.close();
}

function onSelectAll() {
  for (const item of props.items) item.selected = true;
  if (!focusItem.value) setFocus(focusItemIndex.value);

  invalidateSelected();
}

function onUnselectAll() {
  for (const item of props.items) item.selected = false;
  if (focusItem.value) focusItem.value = null;

  invalidateSelected();
}

function setFocus(index) {
  if (props.items[focusItemIndex.value]) props.items[focusItemIndex.value].focused = false;
  if (focusItem.value) focusItem.value.focused = false;
  if (!props.items[index]) return;

  focusItem.value = props.items[index];
  focusItem.value.focused = true;
  focusItemIndex.value = index;
}

function onSelectAndFocusToIndex(index) {
  index = index < 0 ? 0 : index > props.items.length - 1 ? props.items.length - 1 : index;

  setFocus(index);

  const item = props.items[index];
  const firstSelectedIndex = props.items.findIndex((i) => i.selected);
  const lastSelectedIndex = props.items.findLastIndex((i) => i.selected);

  if (index < firstSelectedIndex) for (let i = index; i < firstSelectedIndex; ++i) props.items[i].selected = true;
  else if (index > lastSelectedIndex) for (let i = lastSelectedIndex; i <= index; ++i) props.items[i].selected = true;

  scrollItemIntoView(elements[item.id]);

  invalidateSelected();
}

function onSelectAndFocusIndex(index, keepSelected = false) {
  index = index < 0 ? 0 : index > props.items.length - 1 ? props.items.length - 1 : index;

  const item = props.items[index];

  onSelectAndFocus(item, {}, keepSelected);
}

function onSelectAndFocus(item, event, keepSelected = false) {
  const itemIndex = props.items.findIndex((i) => i.id === item.id);
  setFocus(itemIndex);

  if (event.button === 2 && item.selected) keepSelected = true;

  if (event.ctrlKey) {
    if (keepSelected) item.selected = true;
    else item.selected = !item.selected;
  } else if (event.shiftKey) {
    const firstSelectedIndex = props.items.findIndex((i) => i.selected);
    const lastSelectedIndex = props.items.findLastIndex((i) => i.selected);

    if (itemIndex < firstSelectedIndex) for (let i = itemIndex; i < firstSelectedIndex; ++i) props.items[i].selected = true;
    else if (itemIndex > lastSelectedIndex) for (let i = lastSelectedIndex; i <= itemIndex; ++i) props.items[i].selected = true;
  } else {
    if (!keepSelected) for (const it of props.items) it.selected = false;
    item.selected = true;
  }

  scrollItemIntoView(elements[item.id]);

  invalidateSelected();
}

const contextMenuBodyModel = [
  {
    label: props.tr('filemanager.list.menu.paste'),
    icon: 'fa-regular fa-paste',
    disabled: () => !clipboard.files || !clipboard.files.length,
    action: () => {
      props.pasteHandler(clipboard.action, clipboard.files, null);
    },
  },
  {
    label: props.tr('filemanager.list.menu.selectAll'),
    icon: 'fa-solid fa-check-double',
    action: onSelectAll,
  },
  {
    separator: true,
    visible: () => typeof props.refreshHandler === 'function',
  },
  {
    label: props.tr('filemanager.toolbar.refresh'),
    icon: 'fa-solid fa-arrow-rotate-right',
    visible: () => typeof props.refreshHandler === 'function',
    action: props.refreshHandler,
  },
  {
    separator: true,
    visible: () => props.showNewFile || props.showNewFolder,
  },
  {
    label: props.tr('filemanager.toolbar.newFile'),
    icon: 'fa-solid fa-file-circle-plus',
    action: props.newFileHandler,
    visible: () => props.showNewFile,
  },
  {
    label: props.tr('filemanager.toolbar.newFolder'),
    icon: 'fa-solid fa-folder-plus',
    action: props.newFolderHandler,
    visible: () => props.showNewFolder,
  },
  {
    separator: true,
    visible: () => props.showUploadFile || props.showUploadFolder,
  },
  {
    label: props.tr('filemanager.toolbar.uploadFile'),
    icon: 'fa-solid fa-file-arrow-up',
    action: props.uploadFileHandler,
    visible: () => props.showUploadFile,
  },
  {
    label: props.tr('filemanager.toolbar.uploadFolder'),
    icon: 'fa-regular fa-folder-open',
    action: props.uploadFolderHandler,
    visible: () => props.showUploadFolder,
  },
];

const contextMenuModel = [
  {
    label: props.tr('filemanager.list.menu.open'),
    icon: 'fa-regular fa-folder-open',
    disabled: () => selectedCount.value > 1,
    action: () => {
      onItemActivated(getSelected()[0]);
    },
  },
  {
    separator: true,
    visible: () =>
      props.showDownload ||
      props.showShare ||
      props.showCopy ||
      props.showCut ||
      props.showPaste ||
      props.showSelectAll,
  },
  {
    label: props.tr('filemanager.list.menu.download'),
    icon: 'fa-solid fa-download',
    action: onItemDownload,
    visible: () => props.showDownload,
    disabled: () => (props.multiDownload ? selectedCount.value === 0 : selectedCount.value !== 1),
  },
  {
    label: props.tr('filemanager.list.menu.share'),
    icon: 'fa-solid fa-share-nodes',
    action: onItemShare,
    disabled: () => selectedCount.value > 1,
    visible: () => props.showShare,
  },
  {
    label: props.tr('filemanager.list.menu.copy'),
    icon: 'fa-regular fa-copy',
    action: onItemsCopy,
    visible: () => props.showCopy,
  },
  {
    label: props.tr('filemanager.list.menu.cut'),
    icon: 'fa-solid fa-scissors',
    action: onItemsCut,
    visible: () => props.showCut,
  },
  {
    label: props.tr('filemanager.list.menu.paste'),
    icon: 'fa-regular fa-paste',
    visible: () => props.showPaste,
    disabled: () =>
      !(focusItem.value && focusItem.value.isDirectory) ||
      selectedCount.value > 1 ||
      !clipboard.files ||
      !clipboard.files.length,
    action: () => {
      props.pasteHandler(clipboard.action, clipboard.files, focusItem.value);
    },
  },
  {
    label: props.tr('filemanager.list.menu.selectAll'),
    icon: 'fa-solid fa-check-double',
    visible: () => props.showSelectAll,
    action: onSelectAll,
  },
  {
    label: props.tr('filemanager.list.menu.rename'),
    icon: 'fa-regular fa-pen-to-square',
    action: onItemRenameBegin,
    visible: () => props.showRename,
    disabled: () => !props.editable || selectedCount.value > 1,
  },
  {
    label: props.tr('filemanager.list.menu.chown'),
    icon: 'fa-solid fa-user-pen',
    action: onItemsChangeOwner,
    visible: () => props.showOwner,
  },
  {
    label: props.tr('filemanager.list.menu.extract'),
    action: onItemExtract,
    visible: () => {
      if (!props.showExtract) return false;

      const file = getSelected()[0];
      return !(
        selectedCount.value !== 1 ||
        !file ||
        !(
          file.name.match(/\.tgz$/) ||
          file.name.match(/\.tar$/) ||
          file.name.match(/\.7z$/) ||
          file.name.match(/\.zip$/) ||
          file.name.match(/\.tar\.gz$/) ||
          file.name.match(/\.tar\.xz$/) ||
          file.name.match(/\.tar\.bz2$/)
        )
      );
    },
  },
  {
    separator: true,
    visible: () => props.showDelete,
  },
  {
    label: props.tr('filemanager.list.menu.delete'),
    icon: 'fa-regular fa-trash-can',
    action: () => {
      props.deleteHandler(getSelected());
    },
    visible: () => props.showDelete,
  },
];

const filteredSortedItems = computed(() => {
  invalidateSelected();

  return props.items.sort((a, b) => {
    if (a.isDirectory && !b.isDirectory) return -1;
    if (!a.isDirectory && b.isDirectory) return 1;

    let sort = 0;
    if (sortBy.value === SORT_BY.NAME) {
      if (a.name.toLowerCase() < b.name.toLowerCase()) sort = -1;
      else if (a.name.toLowerCase() > b.name.toLowerCase()) sort = 1;
    } else if (sortBy.value === SORT_BY.OWNER) {
      if (a.owner.toLowerCase() < b.owner.toLowerCase()) sort = -1;
      else if (a.owner.toLowerCase() > b.owner.toLowerCase()) sort = 1;
    } else if (sortBy.value === SORT_BY.STAR) {
      if (a.star && !b.star) sort = -1;
      if (!a.star && b.star) sort = 1;
    } else if (sortBy.value === SORT_BY.SIZE) {
      if (a.size < b.size) sort = -1;
      if (a.size > b.size) sort = 1;
    } else if (sortBy.value === SORT_BY.MTIME) {
      if (a.mtime < b.mtime) sort = -1;
      if (a.mtime > b.mtime) sort = 1;
    } else {
      console.error('unknown SORT_BY', sortBy.value);
    }

    if (sort === 0) {
      if (a.id < b.id) sort = -1;
      else sort = 1;
    }

    return (sort *= sortDirection.value === SORT_DIRECTION.DESC ? -1 : 1);
  });
});

defineExpose({
  highlight,
  highlightByName,
});

</script>
<style scoped>

.directory-view {
  display: block;
  overflow: hidden;
  height: 100%;
  width: 100%;
  user-select: none;
  border: 2px solid transparent;
}

.directory-view-empty-container {
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  height: 100%;
  width: 100%;
  user-select: none;
  border: 2px solid transparent;
}

.directory-view.drop-target-active {
  border: 2px solid var(--pankow-color-primary);
}

.directory-view-header {
  padding: 10px;
  padding-top: 20px;
  display: flex;
  font-weight: var(--pankow-font-weight-bold);
}

.directory-view-body-container {
  overflow: hidden;
  height: calc(100% - 38px); /* 38px is the header size */
}

.directory-view-body-container-full {
  height: 100%;
}

.directory-view-body {
  padding: 0 10px;
  padding-bottom: 10px;
  height: 100%;
  overflow: auto;
}

.directory-view-body-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 4px;
  align-content: start;
}

.directory-view-header-icon {
  width: 40px;
}

.directory-view-header-name {
  padding-left: 10px;
  flex-grow: 1;
}

.directory-view-header-star {
  width: 80px;
  text-align: center;
}

.directory-view-header-owner {
  width: 100px;
}

.directory-view-header-size {
  width: 100px;
}

.directory-view-header-modified {
  width: 100px;
}

.directory-view-header-actions {
  width: 40px;
}

.dialog-header {
  font-weight: 600;
  font-size: 1.25rem;
}

.dialog-dropdown {
  width: 100%;
  margin-top: 5px;
  margin-bottom: 1.5rem;
}

.dialog-submit {
  margin-top: 5px;
}

.drag-handle {
  position: absolute;
  top: -1000px;
  background: transparent;
}

.drag-handle :deep(div) {
  display: flex;
  align-items: center;
}

.drag-handle :deep(img) {
  width: 45px;
  height: 45px;
  object-fit: cover;
  padding: 5px;
}

</style>
