<template>
    <div class="tree-node">
        <!-- Directory row: arrow toggles tree, name selects directory -->
        <div
            v-if="!hideSelf"
            class="tree-row dir-row"
            :class="{ active: path === activePath, 'is-leaf': isLeaf }"
            :style="{ paddingLeft: `${depth * 12 + 14}px` }"
            @click.stop="selectDir"
        >
            <span v-if="!isLeaf" class="chevron" @click.stop="toggle">
                <ChevronDownIcon v-if="open" class="icon w-3 h-3" />
                <ChevronRightIcon v-else class="icon w-3 h-3" />
            </span>
            <span v-else class="chevron-spacer"></span>
            <span class="label">{{ label }}</span>
        </div>

        <!-- Children (always visible when hideSelf) -->
        <div v-if="open || hideSelf" class="tree-children">
            <div v-if="showLoading" class="tree-loading" :style="{ paddingLeft: `${childDepth * 12 + 14}px` }">…</div>
            <div v-else-if="error" class="tree-error" :style="{ paddingLeft: `${childDepth * 12 + 14}px` }">{{ error }}</div>
            <template v-else>
                <!-- Subdirectories (recursive) -->
                <TreeNode
                    v-for="dir in dirs"
                    :key="path + '/' + dir"
                    :label="dir"
                    :path="path + '/' + dir"
                    :token="token"
                    :active-path="activePath"
                    :expand-to="expandTo"
                    :depth="childDepth"
                    :folders-only="foldersOnly"
                    @select="$emit('select', $event)"
                />
                <!-- Files (hidden in foldersOnly mode) -->
                <template v-if="!foldersOnly">
                    <div
                        v-for="file in files"
                        :key="file.path"
                        class="tree-row file-row"
                        :class="{ active: file.path === activePath }"
                        :style="{ paddingLeft: `${childDepth * 12 + 14}px` }"
                        @click="$emit('select', file.path)"
                    >
                        <DocumentIcon class="icon file-icon w-3 h-3" />
                        <span class="label">{{ file.name }}</span>
                    </div>
                </template>
                <div v-if="!loading && !dirs.length && !foldersOnly && !files.length" class="tree-empty" :style="{ paddingLeft: `${childDepth * 12 + 14}px` }">empty</div>
            </template>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import { ChevronDownIcon, ChevronRightIcon, DocumentIcon } from '@heroicons/vue/20/solid';
import { viewerOpenDirs, useViewerStore } from '../store';
import { getApiBase } from '../utils/apiBase';
import { ws } from '../store';

interface FileEntry { name: string; path: string; mtime: number; }

const props = defineProps<{
    label: string;
    path: string;
    token: string;
    activePath: string;
    expandTo: string;   // path to auto-expand toward (e.g. on F5 load)
    depth: number;
    hideSelf?: boolean; // hide own dir row, show children directly (used for root-less trees)
    foldersOnly?: boolean; // only show directories, hide files
}>();

const emit = defineEmits<{ (e: 'select', path: string): void }>();

// When hideSelf, children render at same depth (no parent row taking space)
const childDepth = computed(() => props.hideSelf ? props.depth : props.depth + 1);

// Track whether we've fetched at least once
const fetched = ref(false);
// In foldersOnly mode: show chevron only when we KNOW there are subdirs
// Before fetch: no chevron (prevent flicker). After fetch: chevron only if dirs exist.
const hasSubdirs = computed(() => dirs.value.length > 0);
const isLeaf = computed(() => props.foldersOnly ? !hasSubdirs.value : false);

const viewerStore = useViewerStore();

// open state is driven by the shared store — survives tab switches
const open = computed(() => viewerOpenDirs.has(props.path));
const loading = ref(false);
const showLoading = ref(false);
let loadingTimer: ReturnType<typeof setTimeout> | null = null;
const error = ref('');
const dirs = ref<string[]>([]);
const files = ref<FileEntry[]>([]);

function getBaseUrl(): string {
    return getApiBase();
}

async function fetchTree(_retry = false) {
    if (loading.value) return;
    loading.value = true;
    showLoading.value = false;
    if (loadingTimer) clearTimeout(loadingTimer);
    loadingTimer = setTimeout(() => { showLoading.value = true; }, 3000);
    error.value = '';
    try {
        const base = getBaseUrl();
        const url = `${base}/api/viewer/tree?root=${encodeURIComponent(props.path)}&token=${encodeURIComponent(props.token)}`;
        const res = await fetch(url);
        if (res.status === 401 && !_retry) {
            loading.value = false;
            viewerStore.invalidate();
            await viewerStore.acquire(true);
            return fetchTree(true);
        }
        if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
        const data = await res.json();
        dirs.value = data.dirs || [];
        files.value = data.files || [];
    } catch (e: any) {
        error.value = e.message || 'Failed to load';
    } finally {
        loading.value = false;
        showLoading.value = false;
        fetched.value = true;
        if (loadingTimer) { clearTimeout(loadingTimer); loadingTimer = null; }
    }
}

function setOpen(val: boolean) {
    if (val) viewerOpenDirs.add(props.path);
    else viewerOpenDirs.delete(props.path);
}

function toggle() {
    const next = !open.value;
    setOpen(next);
    if (next && !dirs.value.length && !files.value.length && !loading.value && !error.value) {
        fetchTree();
    }
}

function selectDir() {
    // Select directory (show contents in viewer) without toggling tree
    emit('select', props.path);
}

// Auto-expand toward expandTo — opens this dir and fetches children if needed
function maybeExpand(expandTo: string) {
    if (expandTo && expandTo.startsWith(props.path + '/') && !open.value) {
        setOpen(true);
        // Fetch children so subdirs mount and can expand further down the path
        if (!dirs.value.length && !files.value.length) fetchTree();
    }
}
// Listen for directory changes (new/deleted files)
const { onMessage } = ws;
let unsubDirWatch: (() => void) | null = null;

onMounted(() => {
    // hideSelf: force-open this node so children always render
    if (props.hideSelf && !open.value) setOpen(true);
    // Restore previously-open dir (from store) — fetch children so subtree renders
    if ((open.value || props.hideSelf) && !dirs.value.length && !files.value.length) fetchTree();
    // foldersOnly: pre-fetch to detect leaf dirs (no subdirs → hide chevron)
    else if (props.foldersOnly && !fetched.value && !loading.value) fetchTree();
    maybeExpand(props.expandTo);

    unsubDirWatch = onMessage((data: any) => {
        if (data.type === 'viewer_tree_changed' && data.path === props.path && open.value) {
            fetchTree();
        }
    });
});
onUnmounted(() => { if (unsubDirWatch) unsubDirWatch(); });
watch(() => props.expandTo, maybeExpand);
</script>

<style scoped>
.tree-node { user-select: none; }

.tree-row {
    display: flex;
    align-items: center;
    gap: 4px;
    cursor: pointer;
    padding-top: 2px;
    padding-bottom: 2px;
    padding-right: 10px;
    border-radius: 3px;
    color: var(--text, #ccc);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tree-row:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); }
.tree-row.active { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.dir-row.active { color: var(--text, #ccc); }
.file-row.active { color: var(--text, #ccc); }

.dir-row { color: var(--text-dim, #71B095); font-weight: 500; }
.chevron { display: flex; align-items: center; cursor: pointer; padding: 2px; flex-shrink: 0; opacity: 0.6; }
.chevron:hover { opacity: 1; }
.chevron-spacer { width: 16px; flex-shrink: 0; }
.icon { flex-shrink: 0; font-style: normal; width: 12px; text-align: center; }
.file-icon { }
.label { overflow: hidden; text-overflow: ellipsis; flex: 1; }

.tree-loading, .tree-error, .tree-empty {
    padding-top: 2px;
    padding-bottom: 2px;
    color: var(--text-dim, #666);
    font-style: italic;
}
.tree-error { color: var(--error, #e06c75); }
</style>
