<template>
  <aside class="script-template-tree script-explorer" @contextmenu.prevent="emitContext($event, null, 'root')">
    <header class="script-explorer-titlebar">
      <strong>Explorer</strong>
      <div>
        <button title="New folder" type="button" @click="$emit('create-folder', null)">
          <FolderPlus :size="15" />
        </button>
        <button title="Explorer actions" type="button" @click.stop="emitContext($event, null, 'root')">
          <MoreHorizontal :size="15" />
        </button>
        <button title="Close Script Library" type="button" @click="$emit('close')">
          <X :size="14" />
        </button>
      </div>
    </header>

    <label class="tree-search script-explorer-search">
      <Search :size="14" />
      <input v-model="query" placeholder="Filter scripts..." />
    </label>

    <div class="script-tree-scroll script-explorer-scroll" role="tree" aria-label="Script library explorer">
      <button
        class="explorer-row explorer-root"
        type="button"
        :aria-expanded="rootExpanded"
        @click="rootExpanded = !rootExpanded"
        @contextmenu.stop.prevent="emitContext($event, null, 'root')"
      >
        <ChevronRight :size="14" :class="{ open: rootExpanded }" />
        <span>POSHManager</span>
        <small>{{ scripts.length }}</small>
      </button>

      <template v-if="rootExpanded">
        <button
          v-for="row in visibleRows"
          :key="row.key"
          :class="[
            'explorer-row',
            `depth-${Math.min(row.depth, 6)}`,
            row.type === 'folder' ? 'explorer-folder' : 'explorer-file',
            { active: row.type === 'script' && selectedScriptId === row.item.id }
          ]"
          type="button"
          role="treeitem"
          :aria-expanded="row.type === 'folder' ? isFolderExpanded(row.item.id) : undefined"
          :style="{ '--depth': row.depth }"
          @click="row.type === 'folder' ? toggleFolder(row.item.id) : $emit('select-script', row.item)"
          @contextmenu.stop.prevent="emitContext($event, row.item, row.type)"
        >
          <ChevronRight v-if="row.type === 'folder'" :size="14" :class="{ open: isFolderExpanded(row.item.id) }" />
          <span v-else class="explorer-spacer"></span>
          <Folder v-if="row.type === 'folder'" :size="15" />
          <FileCode2 v-else :size="14" />
          <span class="explorer-name">{{ row.item.name }}</span>
          <small v-if="row.type === 'script'">PS1</small>
          <small v-else>{{ folderScriptCount(row.item.id) || '' }}</small>
        </button>
      </template>

      <div v-if="rootExpanded && !visibleRows.length" class="explorer-empty">
        <strong>{{ query ? 'No matches' : 'No scripts yet' }}</strong>
        <span>{{ query ? 'Try a different script or folder name.' : 'Create a folder or script to start building the library.' }}</span>
      </div>
    </div>
  </aside>
</template>

<script setup>
import { computed, ref, watch } from 'vue';
import { ChevronRight, FileCode2, Folder, FolderPlus, MoreHorizontal, Search, X } from '@lucide/vue';

const props = defineProps({
  folders: { type: Array, default: () => [] },
  scripts: { type: Array, default: () => [] },
  selectedScriptId: { type: [String, Number], default: null },
  activeFolderId: { type: [String, Number], default: null }
});

const emit = defineEmits(['select-script', 'select-folder', 'create-folder', 'close', 'context-menu']);

const query = ref('');
const rootExpanded = ref(true);
const expandedFolderIds = ref([]);

const normalizedQuery = computed(() => query.value.trim().toLowerCase());

const sortedFolders = computed(() => [...props.folders].sort((a, b) => a.name.localeCompare(b.name)));
const sortedScripts = computed(() => [...props.scripts].sort((a, b) => a.name.localeCompare(b.name)));

const childFoldersByParent = computed(() => groupByParent(sortedFolders.value));
const childScriptsByParent = computed(() => groupByParent(sortedScripts.value, 'folderId'));

const visibleRows = computed(() => {
  const rows = [];
  appendFolderRows(null, 1, rows);
  appendScriptRows(null, 1, rows);
  return rows;
});

watch(() => props.folders.map((folder) => folder.id).join('|'), () => {
  if (!expandedFolderIds.value.length) {
    expandedFolderIds.value = props.folders.map((folder) => folder.id);
    return;
  }
  const validIds = new Set(props.folders.map((folder) => folder.id));
  expandedFolderIds.value = expandedFolderIds.value.filter((id) => validIds.has(id));
}, { immediate: true });

watch(() => props.activeFolderId, (folderId) => {
  if (!folderId) return;
  expandAncestors(folderId);
});

function groupByParent(items, key = 'parentId') {
  return items.reduce((map, item) => {
    const parentId = item[key] || null;
    if (!map.has(parentId)) map.set(parentId, []);
    map.get(parentId).push(item);
    return map;
  }, new Map());
}

function appendFolderRows(parentId, depth, rows) {
  for (const folder of childFoldersByParent.value.get(parentId) || []) {
    if (!shouldShowFolder(folder)) continue;
    rows.push({ key: `folder-${folder.id}`, type: 'folder', item: folder, depth });
    if (isFolderExpanded(folder.id) || normalizedQuery.value) {
      appendFolderRows(folder.id, depth + 1, rows);
      appendScriptRows(folder.id, depth + 1, rows);
    }
  }
}

function appendScriptRows(parentId, depth, rows) {
  for (const script of childScriptsByParent.value.get(parentId) || []) {
    if (!matchesSearch(script)) continue;
    rows.push({ key: `script-${script.id}`, type: 'script', item: script, depth });
  }
}

function shouldShowFolder(folder) {
  if (!normalizedQuery.value) return true;
  if (matchesSearch(folder)) return true;
  return hasMatchingDescendant(folder.id);
}

function hasMatchingDescendant(folderId) {
  const childScripts = childScriptsByParent.value.get(folderId) || [];
  if (childScripts.some(matchesSearch)) return true;
  return (childFoldersByParent.value.get(folderId) || []).some((folder) => matchesSearch(folder) || hasMatchingDescendant(folder.id));
}

function matchesSearch(item) {
  const term = normalizedQuery.value;
  if (!term) return true;
  return [item.name, item.description, item.visibility].filter(Boolean).join(' ').toLowerCase().includes(term);
}

function isFolderExpanded(folderId) {
  return expandedFolderIds.value.includes(folderId);
}

function toggleFolder(folderId) {
  emit('select-folder', folderId);
  expandedFolderIds.value = isFolderExpanded(folderId)
    ? expandedFolderIds.value.filter((id) => id !== folderId)
    : [...expandedFolderIds.value, folderId];
}

function folderScriptCount(folderId) {
  return props.scripts.filter((script) => script.folderId === folderId).length;
}

function expandAncestors(folderId) {
  const folderById = new Map(props.folders.map((folder) => [folder.id, folder]));
  const next = new Set(expandedFolderIds.value);
  let cursor = folderById.get(folderId);
  while (cursor) {
    next.add(cursor.id);
    cursor = cursor.parentId ? folderById.get(cursor.parentId) : null;
  }
  expandedFolderIds.value = [...next];
}

function emitContext(event, item, type) {
  emit('context-menu', { event, item, type });
}
</script>
