<template>
  <div class="tree-view-node">
    <div
      class="tree-view-node-row"
      :class="{ 'active': isActive, 'drop-target': dropTargetActive }"
      :style="{ paddingLeft: (props.depth * 16 + 4) + 'px' }"
      @click="onRowClick"
      @dragover.stop.prevent="onDragOver"
      @dragenter.stop.prevent="onDragEnter"
      @dragleave="onDragLeave"
      @drop.stop.prevent="onDrop"
    >
      <span class="tree-view-node-chevron">
        <i v-if="loading" class="fa-solid fa-spinner fa-spin" />
        <i v-else-if="expanded" class="fa-solid fa-chevron-down" />
        <i v-else class="fa-solid fa-chevron-right" />
      </span>
      <i class="fa-solid tree-view-node-folder-icon" :class="expanded ? 'fa-folder-open' : 'fa-folder'" />
      <span :title="props.entry.name">{{ props.entry.name }}</span>
    </div>
    <div v-if="expanded && children.length > 0" class="tree-view-node-children">
      <TreeViewNode
        v-for="child in children"
        :key="child.name"
        :entry="child"
        :list-files="props.listFiles"
        :active-path="props.activePath"
        :depth="props.depth + 1"
        :fallback-icon="props.fallbackIcon"
        :drop-handler="props.dropHandler"
        @navigate="$emit('navigate', $event)"
      />
    </div>
  </div>
</template>

<script setup>

import { ref, computed, watch, onMounted } from 'vue';

const props = defineProps({
  entry: {
    type: Object,
    required: true,
  },
  listFiles: {
    type: Function,
    required: true,
  },
  activePath: {
    type: String,
    default: '/',
  },
  depth: {
    type: Number,
    default: 0,
  },
  fallbackIcon: {
    type: String,
    default: '',
  },
  expandOnMount: {
    type: Boolean,
    default: false,
  },
  dropHandler: {
    type: Function,
    default: null,
  },
});

const emit = defineEmits(['navigate']);

const expanded = ref(false);
const children = ref([]);
const loading = ref(false);
const loaded = ref(false);
const dropTargetActive = ref(false);
let dragExpandTimer = null;

const isActive = computed(() => props.activePath === props.entry.path);

async function expand() {
  if (loading.value) return;

  loading.value = true;

  try {
    const entries = await props.listFiles(props.entry.path);
    children.value = entries
      .filter(item => item.isDirectory)
      .map(item => ({
        name: item.name || item.fileName,
        path: props.entry.path === '/'
          ? '/' + (item.name || item.fileName)
          : props.entry.path + '/' + (item.name || item.fileName),
        isDirectory: true,
        icon: item.icon || props.fallbackIcon,
      }));
    loaded.value = true;
    expanded.value = true;
  } catch (e) {
    console.error('TreeViewNode: failed to load children', e);
  } finally {
    loading.value = false;
  }
}

async function onToggle() {
  if (expanded.value) {
    expanded.value = false;
  } else if (loaded.value) {
    expanded.value = true;
  } else {
    await expand();
  }
}

function onRowClick() {
  onToggle();
  emit('navigate', { path: props.entry.path });
}

function onDragOver(event) {
  if (!props.dropHandler) return;
  event.dataTransfer.dropEffect = 'move';
  dropTargetActive.value = true;
}

function onDragEnter(event) {
  if (!props.dropHandler) return;
  event.dataTransfer.dropEffect = 'move';
  if (!expanded.value) dragExpandTimer = setTimeout(expand, 500);
  dropTargetActive.value = true;
}

function onDragLeave() {
  clearTimeout(dragExpandTimer);
  dropTargetActive.value = false;
}

function onDrop(event) {
  dropTargetActive.value = false;
  if (!props.dropHandler) return;
  clearTimeout(dragExpandTimer);
  props.dropHandler(props.entry.path, event);
}

onMounted(() => {
  if (props.expandOnMount) expand();
});

// Auto-expand if the active path is a descendant of this node
watch(() => props.activePath, (newPath) => {
  const prefix = props.entry.path === '/' ? '/' : props.entry.path + '/';
  if (newPath && newPath !== props.entry.path && newPath.startsWith(prefix)) {
    if (!expanded.value) {
      expand();
    }
  }
}, { immediate: true });

</script>

<style scoped>

.tree-view-node {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.tree-view-node-row {
  display: flex;
  align-items: center;
  cursor: pointer;
  white-space: nowrap;
  border-radius: var(--pankow-border-radius);
  padding: 6px 8px 6px 0;
  margin: 2px 0;
}

.tree-view-node-row:hover {
  background-color: var(--pankow-color-background-hover);
}

.tree-view-node-row.active {
  background-color: #dbedfb;
}

@media (prefers-color-scheme: dark) {
  .tree-view-node-row.active {
    background-color: rgba(255, 255, 255, 0.2);
  }
}

.tree-view-node-row.drop-target {
  background-color: #dbedfb;
  outline: 2px solid var(--pankow-color-primary, #3b82f6);
  outline-offset: -2px;
}

@media (prefers-color-scheme: dark) {
  .tree-view-node-row.drop-target {
    background-color: rgba(255, 255, 255, 0.2);
  }
}

.tree-view-node-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-right: 4px;
  font-size: 10px;
}

.tree-view-node-folder-icon {
  margin-right: 6px;
  color: var(--pankow-color-secondary);
}

</style>
