<template>
  <table v-if="props.documents.length || editing" @blur="cursor = null">
    <thead>
      <tr>
        <th class="selection">
          <input
            type="checkbox"
            tabindex="-1"
            v-model="allSelected"
            :indeterminate="selectionIndeterminate"
          />
        </th>
        <th
          class="sortcolumn"
          :class="{ sortactive: sort === 'name' }"
          @click="toggleSort('name')"
        >
          Name
        </th>
        <th
          class="sortcolumn modified right"
          :class="{ sortactive: sort === 'modified' }"
          @click="toggleSort('modified')"
        >
          Modified
        </th>
        <th
          class="sortcolumn size right"
          :class="{ sortactive: sort === 'size' }"
          @click="toggleSort('size')"
        >
          Size
        </th>
        <th class="menu"></th>
      </tr>
    </thead>
    <tbody>
      <tr v-if="editing?.key === 'new'" class="folder">
        <td class="selection"></td>
        <td class="name">
          <FileRenameInput
            :doc="editing"
            :rename="mkdir"
            :exit="
              () => {
                editing = null
              }
            "
          />
        </td>
        <td class="modified right">{{ editing.modified }}</td>
        <td class="size right">{{ editing.sizedisp }}</td>
        <td class="menu"></td>
      </tr>
      <tr
        v-for="doc of sorted(props.documents as FolderDocument[])"
        :key="doc.key"
        :id="`file-${doc.key}`"
        :class="{
          file: doc.type === 'file',
          folder: doc.type === 'folder',
          cursor: cursor === doc
        }"
        @click="cursor = cursor === doc ? null : doc"
        @contextmenu.prevent="contextMenu($event, doc)"
      >
        <td class="selection" @click.up.stop="cursor = cursor === doc ? doc : null">
          <input
            type="checkbox"
            tabindex="-1"
            :checked="documentStore.selected.has(doc.key)"
            @change="
              ($event.target as HTMLInputElement).checked
                ? documentStore.selected.add(doc.key)
                : documentStore.selected.delete(doc.key)
            "
          />
        </td>
        <td class="name">
          <template v-if="editing === doc"
            ><FileRenameInput
              :doc="doc"
              :rename="rename"
              :exit="
                () => {
                  editing = null
                }
              "
          /></template>
          <template v-else>
            <a
              :href="url_for(doc)"
              tabindex="-1"
              @contextmenu.stop
              @click.stop
              @focus.stop="cursor = doc"
              >{{ doc.name }}</a
            >
            <button @click="() => (editing = doc)">🖊️</button>
          </template>
        </td>
        <td class="modified right">{{ doc.modified }}</td>
        <td class="size right">{{ doc.sizedisp }}</td>
        <td class="menu">
          <button tabindex="-1" @click.stop="cursor = doc; contextMenu($event, doc)">
            ⋮
          </button>
        </td>
      </tr>
    </tbody>
  </table>
  <div v-else class="empty-container">Nothing to see here</div>
</template>

<script setup lang="ts">
import { ref, computed, watchEffect } from 'vue'
import { useDocumentStore } from '@/stores/documents'
import type { Document, FolderDocument } from '@/repositories/Document'
import FileRenameInput from './FileRenameInput.vue'
import createWebSocket from '@/repositories/WS'
import { formatSize, formatUnixDate } from '@/utils'
import { useRouter } from 'vue-router'

const props = withDefaults(
  defineProps<{
    path: Array<string>
    documents: Document[]
  }>(),
  {}
)

const documentStore = useDocumentStore()
const router = useRouter()
const linkBasePath = computed(() => props.path.join('/'))
const filesBasePath = computed(() => `/files/${linkBasePath.value}`)
const url_for = (doc: FolderDocument) =>
  doc.type === 'folder'
    ? `#${linkBasePath.value}/${doc.name}/`
    : `${filesBasePath.value}/${doc.name}`
const cursor = ref<FolderDocument | null>(null)
// File rename
const editing = ref<FolderDocument | null>(null)
const rename = (doc: FolderDocument, newName: string) => {
  const oldName = doc.name
  const control = createWebSocket('/api/control', (ev: MessageEvent) => {
    const msg = JSON.parse(ev.data)
    if ('error' in msg) {
      console.error('Rename failed', msg.error.message, msg.error)
      doc.name = oldName
    } else {
      console.log('Rename succeeded', msg)
    }
  })
  control.onopen = () => {
    control.send(
      JSON.stringify({
        op: 'rename',
        path: `${decodeURIComponent(linkBasePath.value)}/${oldName}`,
        to: newName
      })
    )
  }
  doc.name = newName // We should get an update from watch but this is quicker
}
defineExpose({
  newFolder() {
    const now = Date.now() / 1000
    editing.value = {
      key: 'new',
      name: 'New Folder',
      type: 'folder',
      mtime: now,
      size: 0,
      sizedisp: formatSize(0),
      modified: formatUnixDate(now)
    }
  },
  toggleSelectAll() {
    console.log('Select')
    allSelected.value = !allSelected.value
  },
  toggleSortColumn(column: number) {
    const columns = ['', 'name', 'modified', 'size', '']
    toggleSort(columns[column])
  },
  isCursor() {
    return cursor.value !== null && editing.value === null
  },
  cursorRename() {
    editing.value = cursor.value
  },
  cursorSelect() {
    console.log('select', documentStore.selected)
    const doc = cursor.value
    if (!doc) return
    if (documentStore.selected.has(doc.key)) {
      documentStore.selected.delete(doc.key)
    } else {
      documentStore.selected.add(doc.key)
    }
  },
  cursorMove(d: number) {
    // Move cursor up or down (keyboard navigation)
    const documents = sorted(props.documents as FolderDocument[])
    if (documents.length === 0) {
      cursor.value = null
      return
    }
    const mod = (a: number, b: number) => ((a % b) + b) % b
    const index = cursor.value !== null ? documents.indexOf(cursor.value) : -1
    cursor.value = documents[mod(index + d, documents.length + 1)] ?? null
    const tr = document.getElementById(
      `file-${cursor.value.key}`
    ) as HTMLTableRowElement | null
    // @ts-ignore
    if (tr) tr.scrollIntoView({ block: 'center', behavior: 'instant' })
  }
})
watchEffect(() => {
  if (cursor.value) {
    const a = document.querySelector(
      `#file-${cursor.value.key} .name a`
    ) as HTMLAnchorElement | null
    if (a) a.focus()
  }
})
const mkdir = (doc: FolderDocument, name: string) => {
  const control = createWebSocket('/api/control', (ev: MessageEvent) => {
    const msg = JSON.parse(ev.data)
    if ('error' in msg) {
      console.error('Mkdir failed', msg.error.message, msg.error)
      editing.value = null
    } else {
      console.log('mkdir', msg)
      router.push(`/${linkBasePath.value}/${name}/`)
    }
  })
  control.onopen = () => {
    control.send(
      JSON.stringify({
        op: 'mkdir',
        path: `${decodeURIComponent(linkBasePath.value)}/${name}`
      })
    )
  }
  doc.name = name // We should get an update from watch but this is quicker
}

// Column sort
const toggleSort = (name: string) => {
  sort.value = sort.value === name ? '' : name
}
const sort = ref<string>('')
const sortCompare = {
  name: (a: Document, b: Document) =>
    a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }),
  modified: (a: FolderDocument, b: FolderDocument) => b.mtime - a.mtime,
  size: (a: FolderDocument, b: FolderDocument) => b.size - a.size
}
const sorted = (documents: FolderDocument[]) => {
  const cmp = sortCompare[sort.value as keyof typeof sortCompare]
  const sorted = [...documents]
  if (cmp) sorted.sort(cmp)
  return sorted
}
const selectionIndeterminate = computed({
  get: () => {
    return (
      props.documents.length > 0 &&
      props.documents.some((doc: Document) => documentStore.selected.has(doc.key)) &&
      !allSelected.value
    )
  },
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  set: (value: boolean) => {}
})
const allSelected = computed({
  get: () => {
    return (
      props.documents.length > 0 &&
      props.documents.every((doc: Document) => documentStore.selected.has(doc.key))
    )
  },
  set: (value: boolean) => {
    console.log('Setting allSelected', value)
    for (const doc of props.documents) {
      if (value) {
        documentStore.selected.add(doc.key)
      } else {
        documentStore.selected.delete(doc.key)
      }
    }
  }
})

const contextMenu = (ev: Event, doc: Document) => {
  console.log('Context menu', ev, doc)
}
</script>

<style scoped>
table {
  width: 100%;
  table-layout: fixed;
}
table thead input[type='checkbox'] {
  position: inherit;
  width: 1rem;
  height: 1rem;
}
table tbody input[type='checkbox'] {
  width: 2rem;
  height: 2rem;
}
table .selection {
  width: 1rem;
}
table .modified {
  width: 9rem;
}
table .size {
  width: 4rem;
}
table .menu {
  width: 1rem;
}
tbody td {
  font-size: 1.2rem;
}
table th,
table td {
  padding: 0 0.5rem;
  font-weight: normal;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.name {
  white-space: nowrap;
}
.name button {
  visibility: hidden;
  padding-left: 1rem;
}
.name:hover button {
  visibility: visible;
}
thead tr {
  background: linear-gradient(to bottom, #eee, #fff 30%, #ddd);
  color: #000;
}
tbody tr.cursor {
  background: var(--accent-color);
}
.right {
  text-align: right;
}
.selection {
  width: 2em;
}
.sortcolumn:hover {
  cursor: pointer;
}
.sortcolumn:hover::after {
  color: var(--accent-color);
}
.sortcolumn {
  padding-right: 1.7em;
}
.sortcolumn::after {
  content: '▸';
  color: #888;
  margin: 0 1em 0 0.5em;
  position: absolute;
  transition: all var(--transition-time) linear;
}
.sortactive::after {
  transform: rotate(90deg);
  color: #000;
}
.more-action {
  display: flex;
  flex-direction: column;
  justify-content: start;
}
.action-container {
  display: flex;
  align-items: center;
}
.edit-action {
  min-width: 5%;
}
.carousel-container {
  height: inherit;
}
.name a {
  text-decoration: none;
}
tr {
  height: 2.5rem;
}
tbody .selection input {
  z-index: 1;
  position: absolute;
  opacity: 0;
  left: 0;
}
.selection input:checked {
  opacity: 1;
}
.file .selection::before {
  content: '📄 ';
  font-size: 1.5em;
}
.folder .selection::before {
  content: '📁 ';
  font-size: 1.5em;
}
.empty-container {
  padding-top: 3rem;
  text-align: center;
  font-size: 3rem;
  color: var(--accent-color);
}
</style>
