/**
 * FileTreePanel
 *
 * Left-side VSCode-style file tree for the IDE tab. Uses react-arborist
 * for virtualization, keyboard navigation, and folder expansion. Clicking
 * a file node calls `onOpenFile` so the parent can load it into the
 * editor.
 */
'use client';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useMemo, useRef, useEffect, useState } from 'react';
import { Tree } from 'react-arborist';
import { ChevronDown, ChevronRight, File, FolderClosed, FolderOpen } from 'lucide-react';
import { cn } from '../../../../lib/utils.js';
function toArboristNodes(entries) {
    if (!entries)
        return [];
    return entries.map((e) => ({
        id: e.path || e.name,
        name: e.name,
        isDirectory: e.isDirectory,
        path: e.path,
        children: e.isDirectory ? toArboristNodes(e.children) : undefined,
    }));
}
const callbacksRef = { current: null };
function NodeRenderer({ node, style }) {
    const isDir = node.data.isDirectory;
    const isOpen = node.isOpen;
    return (_jsxs("div", { style: style, className: cn('flex h-6 cursor-pointer items-center gap-1 rounded-sm px-1 text-[12px] select-none', node.isSelected
            ? 'bg-primary/10 text-foreground'
            : 'text-muted-foreground hover:bg-muted/60'), onMouseDown: (e) => {
            if (e.button !== 0)
                return;
            if (isDir) {
                node.toggle();
                return;
            }
            node.select();
            // Single click → preview (italic, reused slot).
            callbacksRef.current?.onOpenFile(node.data.path, 'preview');
        }, onDoubleClick: (e) => {
            if (isDir)
                return;
            e.preventDefault();
            // Double click → promote to a persistent tab.
            callbacksRef.current?.onOpenFile(node.data.path, 'persistent');
        }, children: [isDir ? (isOpen ? (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0" })) : (_jsx(ChevronRight, { className: "h-3 w-3 shrink-0" }))) : (_jsx("span", { className: "inline-block w-3 shrink-0" })), isDir ? (isOpen ? (_jsx(FolderOpen, { className: "h-3.5 w-3.5 shrink-0 text-amber-500" })) : (_jsx(FolderClosed, { className: "h-3.5 w-3.5 shrink-0 text-amber-500" }))) : (_jsx(File, { className: "h-3.5 w-3.5 shrink-0 text-sky-500" })), _jsx("span", { className: "truncate", children: node.data.name })] }));
}
export function FileTreePanel({ tree, loading, error, activePath, onOpenFile, }) {
    const data = useMemo(() => toArboristNodes(tree?.children), [tree]);
    const treeRef = useRef(null);
    const [container, setContainer] = useState(null);
    const [size, setSize] = useState({ w: 280, h: 400 });
    // Keep the module-scoped ref pointed at the latest callback.
    useEffect(() => {
        callbacksRef.current = { onOpenFile };
        return () => {
            if (callbacksRef.current?.onOpenFile === onOpenFile)
                callbacksRef.current = null;
        };
    }, [onOpenFile]);
    // Measure the container so react-arborist can virtualize correctly.
    //
    // We use a callback ref (setContainer) rather than useRef + a single
    // useEffect([], …) because the component early-returns a loading /
    // error placeholder BEFORE the container div gets a chance to mount.
    // With the old pattern the effect ran once on first render with
    // containerRef.current === null, the ResizeObserver never attached,
    // and size.h stayed stuck at the 400 placeholder forever — which
    // produced the "my file list is clipped until I collapse+reopen the
    // explorer" bug, because collapse/reopen remounted the component and
    // this time tree was already loaded so the container div was rendered
    // on the first render pass.
    useEffect(() => {
        if (!container)
            return;
        // Seed size synchronously from the live element — ResizeObserver
        // only fires after a layout tick, and we'd rather not show a flash
        // of the 400px placeholder in the meantime.
        const rect = container.getBoundingClientRect();
        if (rect.width > 0 && rect.height > 0) {
            setSize({ w: Math.floor(rect.width), h: Math.floor(rect.height) });
        }
        const ro = new ResizeObserver((entries) => {
            const entry = entries[0]?.contentRect;
            if (entry)
                setSize({ w: Math.floor(entry.width), h: Math.floor(entry.height) });
        });
        ro.observe(container);
        return () => ro.disconnect();
    }, [container]);
    // IMPORTANT: always render the containerRef div as the outer wrapper,
    // even during loading / error / empty states. Loading and error content
    // render INSIDE it so the ResizeObserver effect above stays attached to
    // a single stable DOM node across the loading→loaded transition — no
    // re-attach flicker, no stale measurement on the first frame after the
    // file tree arrives.
    return (_jsx("div", { ref: setContainer, className: "h-full w-full overflow-hidden", children: loading && !tree ? (_jsx("div", { className: "text-muted-foreground flex h-full items-center justify-center text-xs", children: "Loading files\u2026" })) : error ? (_jsx("div", { className: "text-destructive flex h-full items-center justify-center p-2 text-center text-xs", children: error })) : !tree ? null : (_jsx(Tree, { ref: treeRef, data: data, openByDefault: false, width: size.w, height: size.h, rowHeight: 24, indent: 12, padding: 4, selection: activePath ?? undefined, children: NodeRenderer })) }));
}
