"use client";

import { useState, useCallback, useRef, useEffect } from "react";
import {
  DndContext,
  DragOverlay,
  useDraggable,
  useDroppable,
  closestCenter,
  type DragStartEvent,
  type DragEndEvent,
  type DragOverEvent,
  PointerSensor,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import { ContextMenu, type ContextMenuAction, type ContextMenuTarget } from "./context-menu";
import { InlineRename, RENAME_SHAKE_STYLE } from "./inline-rename";

// --- Types ---

export type TreeNode = {
  name: string;
  path: string;
  type: "object" | "document" | "folder" | "file" | "database" | "report";
  icon?: string;
  defaultView?: "table" | "kanban";
  children?: TreeNode[];
  /** When true, the node represents a virtual folder/file outside the real workspace (e.g. Skills, Memories). CRUD ops are disabled. */
  virtual?: boolean;
  /** True when the entry is a symbolic link / shortcut. */
  symlink?: boolean;
};

/** Folder names reserved for virtual sections -- cannot be created/renamed to. */
const RESERVED_FOLDER_NAMES = new Set(["Chats", "Skills", "Memories"]);

/** Check if a node (or any of its ancestors) is virtual. Paths starting with ~ are always virtual. */
function isVirtualNode(node: TreeNode): boolean {
  return !!node.virtual || node.path.startsWith("~");
}

type FileManagerTreeProps = {
  tree: TreeNode[];
  activePath: string | null;
  onSelect: (node: TreeNode) => void;
  onRefresh: () => void;
  compact?: boolean;
  /** Parent directory path for ".." navigation. Null when at filesystem root or in workspace mode without browsing. */
  parentDir?: string | null;
  /** Callback when user clicks ".." to navigate up. */
  onNavigateUp?: () => void;
  /** Current browse directory (absolute path), or null when in workspace mode. */
  browseDir?: string | null;
  /** Absolute path of the workspace root. Nodes matching this path are rendered as a special non-collapsible workspace entry point. */
  workspaceRoot?: string | null;
  /** Called when a node is dragged and dropped outside the tree onto an external drop target (e.g. chat input). */
  onExternalDrop?: (node: TreeNode) => void;
};

// --- System file detection (client-side mirror) ---

/** Always protected regardless of depth. */
const ALWAYS_SYSTEM_PATTERNS = [
  /^\.object\.yaml$/,
  /\.wal$/,
  /\.tmp$/,
];

/** Only protected at the workspace root (no "/" in the relative path). */
const ROOT_ONLY_SYSTEM_PATTERNS = [
  /^workspace\.duckdb/,
  /^workspace_context\.yaml$/,
];

function isSystemFile(path: string): boolean {
  const base = path.split("/").pop() ?? "";
  if (ALWAYS_SYSTEM_PATTERNS.some((p) => p.test(base))) {return true;}
  const isRoot = !path.includes("/");
  return isRoot && ROOT_ONLY_SYSTEM_PATTERNS.some((p) => p.test(base));
}

// --- Icons (inline SVG, zero-dep) ---

function FolderIcon({ open }: { open?: boolean }) {
  return (
    <img
      src={open ? "/icons/folder-open.png" : "/icons/folder.png"}
      alt=""
      width={16}
      height={16}
      draggable={false}
      style={{ flexShrink: 0 }}
    />
  );
}

function TableIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 3v18" /><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M3 15h18" />
    </svg>
  );
}

function KanbanIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <rect width="6" height="14" x="2" y="5" rx="1" /><rect width="6" height="10" x="9" y="5" rx="1" /><rect width="6" height="16" x="16" y="3" rx="1" />
    </svg>
  );
}

function DocumentIcon() {
  return (
    <img src="/icons/document.png" alt="" width={16} height={16} draggable={false} style={{ flexShrink: 0, filter: "drop-shadow(0 0.5px 1.5px rgba(0,0,0,0.2))" }} />
  );
}

function FileIcon() {
  return (
    <img src="/icons/document.png" alt="" width={16} height={16} draggable={false} style={{ flexShrink: 0, filter: "drop-shadow(0 0.5px 1.5px rgba(0,0,0,0.2))" }} />
  );
}

function DatabaseIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M3 5V19A9 3 0 0 0 21 19V5" /><path d="M3 12A9 3 0 0 0 21 12" />
    </svg>
  );
}

function ReportIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <line x1="12" x2="12" y1="20" y2="10" /><line x1="18" x2="18" y1="20" y2="4" /><line x1="6" x2="6" y1="20" y2="14" />
    </svg>
  );
}

function ChatBubbleIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
    </svg>
  );
}

function LockBadge() {
  return (
    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.4 }}>
      <rect width="18" height="11" x="3" y="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" />
    </svg>
  );
}

function SymlinkBadge() {
  return (
    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.55 }}>
      <path d="m9 18 6-6-6-6" />
    </svg>
  );
}

function ChevronIcon({ open }: { open: boolean }) {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
      style={{ transform: open ? "rotate(90deg)" : "rotate(0deg)", transition: "transform 150ms ease" }}>
      <path d="m9 18 6-6-6-6" />
    </svg>
  );
}

function WorkspaceGridIcon() {
  return (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <rect width="7" height="7" x="3" y="3" rx="1" />
      <rect width="7" height="7" x="14" y="3" rx="1" />
      <rect width="7" height="7" x="14" y="14" rx="1" />
      <rect width="7" height="7" x="3" y="14" rx="1" />
    </svg>
  );
}

function NodeIcon({ node, open }: { node: TreeNode; open?: boolean }) {
  // Chat items use the chat bubble icon
  if (node.path.startsWith("~chats/") || node.path === "~chats") {
    return <ChatBubbleIcon />;
  }
  switch (node.type) {
    case "object":
      return node.defaultView === "kanban" ? <KanbanIcon /> : <TableIcon />;
    case "document":
      return <DocumentIcon />;
    case "folder":
      return <FolderIcon open={open} />;
    case "database":
      return <DatabaseIcon />;
    case "report":
      return <ReportIcon />;
    default:
      return <FileIcon />;
  }
}

function typeColor(node: TreeNode): string {
  switch (node.type) {
    case "object": return "var(--color-accent)";
    case "document": return "#60a5fa";
    case "database": return "#c084fc";
    case "report": return "#22c55e";
    default: return "var(--color-text-muted)";
  }
}

// --- API helpers ---

async function apiRename(path: string, newName: string) {
  const res = await fetch("/api/workspace/rename", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ path, newName }),
  });
  return res.json();
}

async function apiDelete(path: string) {
  const res = await fetch("/api/workspace/file", {
    method: "DELETE",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ path }),
  });
  return res.json();
}

async function apiMove(sourcePath: string, destinationDir: string) {
  const res = await fetch("/api/workspace/move", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ sourcePath, destinationDir }),
  });
  return res.json();
}

async function apiDuplicate(path: string) {
  const res = await fetch("/api/workspace/copy", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ path }),
  });
  return res.json();
}

async function apiMkdir(path: string) {
  const res = await fetch("/api/workspace/mkdir", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ path }),
  });
  return res.json();
}

async function apiCreateFile(path: string, content: string = "") {
  const res = await fetch("/api/workspace/file", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ path, content }),
  });
  return res.json();
}

// --- Confirm dialog ---

function ConfirmDialog({
  message,
  onConfirm,
  onCancel,
}: {
  message: string;
  onConfirm: () => void;
  onCancel: () => void;
}) {
  useEffect(() => {
    function handleKey(e: KeyboardEvent) {
      if (e.key === "Escape") {onCancel();}
      if (e.key === "Enter") {onConfirm();}
    }
    document.addEventListener("keydown", handleKey);
    return () => document.removeEventListener("keydown", handleKey);
  }, [onConfirm, onCancel]);

  return (
    <div className="fixed inset-0 z-[10000] flex items-center justify-center" style={{ background: "rgba(0,0,0,0.5)" }}>
      <div className="rounded-xl p-5 max-w-sm w-full shadow-2xl border" style={{ background: "var(--color-surface)", borderColor: "var(--color-border)" }}>
        <p className="text-sm mb-4" style={{ color: "var(--color-text)" }}>{message}</p>
        <div className="flex justify-end gap-2">
          <button
            type="button"
            onClick={onCancel}
            className="px-3 py-1.5 rounded-md text-sm"
            style={{ color: "var(--color-text-muted)", background: "var(--color-surface-hover)" }}
          >
            Cancel
          </button>
          <button
            type="button"
            onClick={onConfirm}
            className="px-3 py-1.5 rounded-md text-sm text-white"
            style={{ background: "#ef4444" }}
          >
            Delete
          </button>
        </div>
      </div>
    </div>
  );
}

// --- New item prompt ---

function NewItemPrompt({
  kind,
  parentPath,
  onSubmit,
  onCancel,
}: {
  kind: "file" | "folder";
  parentPath: string;
  onSubmit: (name: string) => void;
  onCancel: () => void;
}) {
  const [value, setValue] = useState(kind === "file" ? "untitled.md" : "new-folder");
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    const el = inputRef.current;
    if (!el) {return;}
    el.focus();
    if (kind === "file") {
      const dot = value.lastIndexOf(".");
      el.setSelectionRange(0, dot > 0 ? dot : value.length);
    } else {
      el.select();
    }
  }, []);

  return (
    <div className="fixed inset-0 z-[10000] flex items-center justify-center" style={{ background: "rgba(0,0,0,0.5)" }}>
      <div className="rounded-xl p-5 max-w-sm w-full shadow-2xl border" style={{ background: "var(--color-surface)", borderColor: "var(--color-border)" }}>
        <p className="text-sm mb-3 font-medium" style={{ color: "var(--color-text)" }}>
          New {kind} in <span style={{ color: "var(--color-accent)" }}>{parentPath || "/"}</span>
        </p>
        <input
          ref={inputRef}
          type="text"
          value={value}
          onChange={(e) => setValue(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter") {onSubmit(value.trim());}
            if (e.key === "Escape") {onCancel();}
          }}
          className="w-full px-3 py-2 rounded-md text-sm outline-none border"
          style={{ background: "var(--color-bg)", color: "var(--color-text)", borderColor: "var(--color-border)" }}
        />
        <div className="flex justify-end gap-2 mt-3">
          <button type="button" onClick={onCancel} className="px-3 py-1.5 rounded-md text-sm" style={{ color: "var(--color-text-muted)", background: "var(--color-surface-hover)" }}>
            Cancel
          </button>
          <button type="button" onClick={() => onSubmit(value.trim())} className="px-3 py-1.5 rounded-md text-sm text-white" style={{ background: "var(--color-accent)" }}>
            Create
          </button>
        </div>
      </div>
    </div>
  );
}

// --- Draggable + Droppable Node ---

function DraggableNode({
  node,
  depth,
  activePath,
  selectedPath,
  onSelect,
  onNodeSelect,
  expandedPaths,
  onToggleExpand,
  renamingPath,
  onStartRename,
  onCommitRename,
  onCancelRename,
  onContextMenu,
  compact,
  dragOverPath,
  workspaceRoot,
}: {
  node: TreeNode;
  depth: number;
  activePath: string | null;
  selectedPath: string | null;
  onSelect: (node: TreeNode) => void;
  onNodeSelect: (path: string) => void;
  expandedPaths: Set<string>;
  onToggleExpand: (path: string) => void;
  renamingPath: string | null;
  onStartRename: (path: string) => void;
  onCommitRename: (newName: string) => void;
  onCancelRename: () => void;
  onContextMenu: (e: React.MouseEvent, node: TreeNode) => void;
  compact?: boolean;
  dragOverPath: string | null;
  workspaceRoot?: string | null;
}) {
  // Workspace root in browse mode: non-expandable entry point back to workspace
  const isWorkspaceRoot = !!workspaceRoot && node.path === workspaceRoot;
  const hasChildren = node.children && node.children.length > 0;
  const isExpandable = isWorkspaceRoot ? false : (hasChildren || node.type === "folder" || node.type === "object");
  const isExpanded = isWorkspaceRoot ? false : expandedPaths.has(node.path);
  const isActive = activePath === node.path;
  const isSelected = selectedPath === node.path;
  const isRenaming = renamingPath === node.path;
  const isSysFile = isSystemFile(node.path);
  const isVirtual = isVirtualNode(node);
  const isProtected = isSysFile || isVirtual || isWorkspaceRoot;
  const isDragOver = dragOverPath === node.path && isExpandable;

  const { attributes, listeners, setNodeRef: setDragRef, isDragging } = useDraggable({
    id: `drag-${node.path}`,
    data: { node },
    disabled: isProtected,
  });

  const { setNodeRef: setDropRef, isOver } = useDroppable({
    id: `drop-${node.path}`,
    data: { node },
    disabled: !isExpandable || isVirtual,
  });

  const handleClick = useCallback(() => {
    onNodeSelect(node.path);
    onSelect(node);
    if (isExpandable) {
      onToggleExpand(node.path);
    }
  }, [node, isExpandable, onSelect, onNodeSelect, onToggleExpand]);

  const handleDoubleClick = useCallback(() => {
    if (!isProtected) {
      onStartRename(node.path);
    }
  }, [node.path, isProtected, onStartRename]);

  const handleContextMenu = useCallback(
    (e: React.MouseEvent) => {
      e.preventDefault();
      e.stopPropagation();
      onNodeSelect(node.path);
      onContextMenu(e, node);
    },
    [node, onNodeSelect, onContextMenu],
  );

  // Merge drag + drop refs
  const mergedRef = useCallback(
    (el: HTMLElement | null) => {
      setDragRef(el);
      setDropRef(el);
    },
    [setDragRef, setDropRef],
  );

  const showDropHighlight = (isOver || isDragOver) && isExpandable;

  return (
    <div style={{ opacity: isDragging ? 0.4 : 1 }}>
      <div
        ref={mergedRef}
        {...attributes}
        {...listeners}
        role="treeitem"
        tabIndex={-1}
        draggable={!isProtected}
        onDragStart={(e) => {
          // Native HTML5 drag for cross-component drops (e.g. into chat editor).
          // Coexists with @dnd-kit which uses pointer events for intra-tree reordering.
          e.dataTransfer.setData(
            "application/x-file-mention",
            JSON.stringify({ name: node.name, path: node.path }),
          );
          e.dataTransfer.setData("text/plain", node.path);
          e.dataTransfer.effectAllowed = "copy";
        }}
        onClick={handleClick}
        onDoubleClick={handleDoubleClick}
        onContextMenu={handleContextMenu}
        className="w-full flex items-center gap-1.5 py-1 px-2 rounded-md text-left text-sm transition-colors duration-100 cursor-pointer select-none"
        style={{
          paddingLeft: `${depth * 16 + 8}px`,
          background: isWorkspaceRoot
            ? "var(--color-accent-light)"
            : showDropHighlight
              ? "var(--color-accent-light)"
              : isSelected
                ? "var(--color-surface-hover)"
                : isActive
                  ? "var(--color-surface-hover)"
                  : "transparent",
          color: isWorkspaceRoot
            ? "var(--color-accent)"
            : isActive || isSelected ? "var(--color-text)" : "var(--color-text-muted)",
          outline: isWorkspaceRoot
            ? "1.5px solid var(--color-accent)"
            : showDropHighlight ? "1px dashed var(--color-accent)" : "none",
          outlineOffset: "-1px",
          borderRadius: isWorkspaceRoot ? "8px" : "6px",
          marginTop: isWorkspaceRoot ? "2px" : undefined,
          marginBottom: isWorkspaceRoot ? "2px" : undefined,
        }}
        onMouseEnter={(e) => {
          if (isWorkspaceRoot) {
            (e.currentTarget as HTMLElement).style.opacity = "0.8";
          } else if (!isActive && !isSelected && !showDropHighlight) {
            (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)";
          }
        }}
        onMouseLeave={(e) => {
          if (isWorkspaceRoot) {
            (e.currentTarget as HTMLElement).style.opacity = "1";
          } else if (!isActive && !isSelected && !showDropHighlight) {
            (e.currentTarget as HTMLElement).style.background = "transparent";
          }
        }}
      >
        {/* Expand/collapse chevron – intercept click so it only toggles without navigating */}
        <span
          className="flex-shrink-0 w-4 h-4 flex items-center justify-center"
          style={{ opacity: isExpandable ? 1 : 0, cursor: isExpandable ? "pointer" : undefined }}
          onClick={isExpandable ? (e) => { e.stopPropagation(); onToggleExpand(node.path); } : undefined}
        >
          {isExpandable && <ChevronIcon open={isExpanded} />}
        </span>

        {/* Icon */}
        <span className="flex-shrink-0 flex items-center" style={{ color: isWorkspaceRoot ? "var(--color-accent)" : typeColor(node) }}>
          {isWorkspaceRoot ? <WorkspaceGridIcon /> : <NodeIcon node={node} open={isExpanded} />}
        </span>

        {/* Label or rename input */}
        {isRenaming ? (
          <InlineRename
            currentName={node.name}
            onCommit={onCommitRename}
            onCancel={onCancelRename}
          />
        ) : (
          <span className="truncate flex-1">{node.name}</span>
        )}

        {/* Workspace badge for the workspace root entry point */}
        {isWorkspaceRoot && (
          <span className="text-[10px] px-1.5 py-0.5 rounded-full flex-shrink-0 font-medium"
            style={{ background: "var(--color-accent)", color: "white" }}>
            workspace
          </span>
        )}

        {/* Lock badge for system/virtual files (skip for workspace root -- it has its own badge) */}
        {isProtected && !isWorkspaceRoot && !compact && (
          <span className="flex-shrink-0 ml-1">
            <LockBadge />
          </span>
        )}

        {/* Symlink indicator */}
        {node.symlink && !compact && (
          <span className="flex-shrink-0 ml-0.5" title="Symbolic link" style={{ color: "var(--color-text-muted)" }}>
            <SymlinkBadge />
          </span>
        )}

        {/* Type badge for objects */}
        {node.type === "object" && (
          <span className="text-[10px] px-1.5 py-0.5 rounded-full flex-shrink-0"
            style={{ background: "var(--color-accent-light)", color: "var(--color-accent)" }}>
            {node.defaultView === "kanban" ? "board" : "table"}
          </span>
        )}
      </div>

      {/* Children */}
      {isExpanded && hasChildren && (
        <div className="relative" style={{
          borderLeft: depth > 0 ? "1px solid var(--color-border)" : "none",
          marginLeft: `${depth * 16 + 16}px`,
        }}>
          {node.children!.map((child) => (
            <DraggableNode
              key={child.path}
              node={child}
              depth={depth + 1}
              activePath={activePath}
              selectedPath={selectedPath}
              onSelect={onSelect}
              onNodeSelect={onNodeSelect}
              expandedPaths={expandedPaths}
              onToggleExpand={onToggleExpand}
              renamingPath={renamingPath}
              onStartRename={onStartRename}
              onCommitRename={onCommitRename}
              onCancelRename={onCancelRename}
              onContextMenu={onContextMenu}
              compact={compact}
              dragOverPath={dragOverPath}
              workspaceRoot={workspaceRoot}
            />
          ))}
        </div>
      )}
    </div>
  );
}

// --- Root drop zone (allows dropping items back to the top level) ---

function RootDropZone({ isDragging }: { isDragging: boolean }) {
  const { setNodeRef, isOver } = useDroppable({
    id: "drop-__root__",
    data: { rootDrop: true },
  });

  const showHighlight = isOver && isDragging;

  return (
    <div
      ref={setNodeRef}
      className="flex-1 min-h-[48px]"
      style={{
        margin: isDragging ? "4px 8px" : undefined,
        borderRadius: "6px",
        border: showHighlight ? "1.5px dashed var(--color-accent)" : isDragging ? "1.5px dashed var(--color-border)" : "1.5px dashed transparent",
        background: showHighlight ? "var(--color-accent-light)" : "transparent",
        transition: "all 150ms ease",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      {isDragging && (
        <span className="text-[11px] select-none" style={{ color: showHighlight ? "var(--color-accent)" : "var(--color-text-muted)", opacity: showHighlight ? 1 : 0.6 }}>
          Drop here to move to root
        </span>
      )}
    </div>
  );
}

// --- Drag Overlay ---

function DragOverlayContent({ node }: { node: TreeNode }) {
  return (
    <div
      className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm shadow-lg border"
      style={{
        background: "var(--color-surface)",
        borderColor: "var(--color-border)",
        color: "var(--color-text)",
        pointerEvents: "none",
      }}
    >
      <span style={{ color: typeColor(node) }}>
        <NodeIcon node={node} />
      </span>
      <span>{node.name}</span>
    </div>
  );
}

// --- Helper: find node by path ---

function findNode(tree: TreeNode[], path: string): TreeNode | null {
  for (const n of tree) {
    if (n.path === path) {return n;}
    if (n.children) {
      const found = findNode(n.children, path);
      if (found) {return found;}
    }
  }
  return null;
}

// --- Helper: get parent path ---

function parentPath(path: string): string {
  const parts = path.split("/");
  parts.pop();
  return parts.join("/") || ".";
}

// --- Flatten tree for keyboard navigation ---

function flattenVisible(tree: TreeNode[], expanded: Set<string>): TreeNode[] {
  const result: TreeNode[] = [];
  function walk(nodes: TreeNode[]) {
    for (const n of nodes) {
      result.push(n);
      if (n.children && expanded.has(n.path)) {
        walk(n.children);
      }
    }
  }
  walk(tree);
  return result;
}

// --- Main Exported Component ---

export function FileManagerTree({ tree, activePath, onSelect, onRefresh, compact, parentDir, onNavigateUp, browseDir: _browseDir, workspaceRoot, onExternalDrop }: FileManagerTreeProps) {
  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => new Set());
  const [selectedPath, setSelectedPath] = useState<string | null>(null);
  const [renamingPath, setRenamingPath] = useState<string | null>(null);
  const [dragOverPath, setDragOverPath] = useState<string | null>(null);
  const [activeNode, setActiveNode] = useState<TreeNode | null>(null);

  // Track pointer position during @dnd-kit drags for cross-component drops.
  // Installed synchronously in handleDragStart (not useEffect) to avoid
  // missing early pointer moves. Capture-phase on window fires before
  // @dnd-kit's own document-level listener.
  const pointerPosRef = useRef({ x: 0, y: 0 });
  const pointerListenerRef = useRef<((e: PointerEvent) => void) | null>(null);

  const installPointerTracker = useCallback(() => {
    const handler = (e: PointerEvent) => {
      pointerPosRef.current = { x: e.clientX, y: e.clientY };

      // Toggle visual drop indicator on external chat drop target
      const el = document.elementFromPoint(e.clientX, e.clientY);
      const target = el?.closest("[data-chat-drop-target]") as HTMLElement | null;
      const prev = document.querySelector("[data-drag-hover]");
      if (target && !target.hasAttribute("data-drag-hover")) {
        target.setAttribute("data-drag-hover", "");
      }
      if (prev && prev !== target) {
        prev.removeAttribute("data-drag-hover");
      }
    };
    pointerListenerRef.current = handler;
    window.addEventListener("pointermove", handler, true);
  }, []);

  const removePointerTracker = useCallback(() => {
    if (pointerListenerRef.current) {
      window.removeEventListener("pointermove", pointerListenerRef.current, true);
      pointerListenerRef.current = null;
    }
    document.querySelector("[data-drag-hover]")?.removeAttribute("data-drag-hover");
  }, []);

  // Clean up on unmount
  useEffect(() => removePointerTracker, [removePointerTracker]);

  // Context menu state
  const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number; target: ContextMenuTarget } | null>(null);

  // Confirm dialog
  const [confirmDelete, setConfirmDelete] = useState<string | null>(null);

  // New item prompt
  const [newItemPrompt, setNewItemPrompt] = useState<{ kind: "file" | "folder"; parentPath: string } | null>(null);

  const containerRef = useRef<HTMLDivElement>(null);

  // Auto-expand first level on mount.
  // Keep ~skills and ~memories collapsed by default; always expand ~chats.
  const collapsedByDefault = new Set(["~skills", "~memories"]);
  useEffect(() => {
    if (tree.length > 0 && expandedPaths.size === 0) {
      const initial = new Set<string>();
      for (const node of tree) {
        if (collapsedByDefault.has(node.path)) {continue;}
        if (node.children && node.children.length > 0) {
          initial.add(node.path);
        }
      }
      setExpandedPaths(initial);
    }
  }, [tree]);

  const handleToggleExpand = useCallback((path: string) => {
    setExpandedPaths((prev) => {
      const next = new Set(prev);
      if (next.has(path)) {next.delete(path);}
      else {next.add(path);}
      return next;
    });
  }, []);

  // DnD sensors -- require 8px movement before dragging starts (prevents accidental drags on click)
  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
  );

  const handleDragStart = useCallback((event: DragStartEvent) => {
    const data = event.active.data.current as { node: TreeNode } | undefined;
    if (data?.node) {
      setActiveNode(data.node);
      installPointerTracker();
    }
  }, [installPointerTracker]);

  const handleDragOver = useCallback((event: DragOverEvent) => {
    const overData = event.over?.data.current as { node?: TreeNode; rootDrop?: boolean } | undefined;
    if (overData?.rootDrop) {
      setDragOverPath("__root__");
    } else if (overData?.node) {
      setDragOverPath(overData.node.path);
      // Auto-expand folders on drag hover (300ms delay)
      const path = overData.node.path;
      if (overData.node.type === "folder" || overData.node.type === "object") {
        setTimeout(() => {
          setExpandedPaths((prev) => {
            if (prev.has(path)) {return prev;}
            const next = new Set(prev);
            next.add(path);
            return next;
          });
        }, 300);
      }
    } else {
      setDragOverPath(null);
    }
  }, []);

  const handleDragEnd = useCallback(
    async (event: DragEndEvent) => {
      setActiveNode(null);
      setDragOverPath(null);
      removePointerTracker();

      const activeData = event.active.data.current as { node: TreeNode } | undefined;

      if (!activeData?.node) {return;}

      const source = activeData.node;

      // Check for external drop targets FIRST (e.g. chat input).
      // closestCenter always returns a droppable even when the pointer is
      // far outside the tree, so we can't rely on `event.over === null`.
      if (onExternalDrop) {
        const { x, y } = pointerPosRef.current;
        const el = document.elementFromPoint(x, y);
        if (el?.closest("[data-chat-drop-target]")) {
          onExternalDrop(source);
          return;
        }
      }

      const overData = event.over?.data.current as { node?: TreeNode; rootDrop?: boolean } | undefined;

      // Drop onto root level
      if (overData?.rootDrop) {
        // Already at root? No-op
        if (parentPath(source.path) === ".") {return;}
        const result = await apiMove(source.path, ".");
        if (result.ok) {
          onRefresh();
        }
        return;
      }

      if (!overData?.node) {return;}

      const target = overData.node;

      // Only drop onto expandable targets (folders/objects)
      if (target.type !== "folder" && target.type !== "object") {return;}

      // Prevent dropping into self or children
      if (target.path === source.path || target.path.startsWith(source.path + "/")) {return;}

      // Prevent no-op moves (already in same parent)
      if (parentPath(source.path) === target.path) {return;}

      const result = await apiMove(source.path, target.path);
      if (result.ok) {
        onRefresh();
      }
    },
    [onRefresh, onExternalDrop, removePointerTracker],
  );

  const handleDragCancel = useCallback(() => {
    setActiveNode(null);
    setDragOverPath(null);
    removePointerTracker();
  }, [removePointerTracker]);

  // Context menu handlers
  const handleContextMenu = useCallback((e: React.MouseEvent, node: TreeNode) => {
    const isSys = isSystemFile(node.path) || isVirtualNode(node);
    const isFolder = node.type === "folder" || node.type === "object";
    setCtxMenu({
      x: e.clientX,
      y: e.clientY,
      target: {
        kind: isFolder ? "folder" : "file",
        path: node.path,
        name: node.name,
        isSystem: isSys,
      },
    });
  }, []);

  const handleEmptyContextMenu = useCallback((e: React.MouseEvent) => {
    e.preventDefault();
    setCtxMenu({
      x: e.clientX,
      y: e.clientY,
      target: { kind: "empty" },
    });
  }, []);

  const handleContextMenuAction = useCallback(
    async (action: ContextMenuAction) => {
      const target = ctxMenu?.target;
      if (!target) {return;}

      switch (action) {
        case "open": {
          if (target.kind !== "empty") {
            const node = findNode(tree, target.path);
            if (node) {onSelect(node);}
          }
          break;
        }
        case "rename": {
          if (target.kind !== "empty") {
            setRenamingPath(target.path);
          }
          break;
        }
        case "duplicate": {
          if (target.kind !== "empty") {
            await apiDuplicate(target.path);
            onRefresh();
          }
          break;
        }
        case "copy": {
          if (target.kind !== "empty") {
            await navigator.clipboard.writeText(target.path);
          }
          break;
        }
        case "delete": {
          if (target.kind !== "empty") {
            setConfirmDelete(target.path);
          }
          break;
        }
        case "newFile": {
          const parent = target.kind === "folder" ? target.path : target.kind === "file" ? parentPath(target.path) : "";
          setNewItemPrompt({ kind: "file", parentPath: parent });
          break;
        }
        case "newFolder": {
          const parent = target.kind === "folder" ? target.path : target.kind === "file" ? parentPath(target.path) : "";
          setNewItemPrompt({ kind: "folder", parentPath: parent });
          break;
        }
        case "getInfo": {
          // Future: show info panel. For now, copy path.
          if (target.kind !== "empty") {
            await navigator.clipboard.writeText(target.path);
          }
          break;
        }
      }
    },
    [ctxMenu, tree, onSelect, onRefresh],
  );

  // Rename handlers
  const handleCommitRename = useCallback(
    async (newName: string) => {
      if (!renamingPath) {return;}
      // Block reserved folder names
      if (RESERVED_FOLDER_NAMES.has(newName)) {
        alert(`"${newName}" is a reserved name and cannot be used.`);
        setRenamingPath(null);
        return;
      }
      const result = await apiRename(renamingPath, newName);
      setRenamingPath(null);
      if (result.ok) {onRefresh();}
    },
    [renamingPath, onRefresh],
  );

  const handleCancelRename = useCallback(() => {
    setRenamingPath(null);
  }, []);

  // Delete confirm
  const handleConfirmDelete = useCallback(async () => {
    if (!confirmDelete) {return;}
    const result = await apiDelete(confirmDelete);
    setConfirmDelete(null);
    if (result.ok) {onRefresh();}
  }, [confirmDelete, onRefresh]);

  // New item submit
  const handleNewItemSubmit = useCallback(
    async (name: string) => {
      if (!newItemPrompt || !name) {return;}

      // Block reserved folder names
      if (RESERVED_FOLDER_NAMES.has(name)) {
        alert(`"${name}" is a reserved name and cannot be used.`);
        return;
      }

      const fullPath = newItemPrompt.parentPath ? `${newItemPrompt.parentPath}/${name}` : name;

      if (newItemPrompt.kind === "folder") {
        await apiMkdir(fullPath);
      } else {
        await apiCreateFile(fullPath, "");
      }

      setNewItemPrompt(null);
      onRefresh();

      // Auto-expand parent
      setExpandedPaths((prev) => {
        const next = new Set(prev);
        next.add(newItemPrompt.parentPath);
        return next;
      });
    },
    [newItemPrompt, onRefresh],
  );

  // Keyboard shortcuts
  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      // Don't capture keyboard events when renaming
      if (renamingPath) {return;}

      const flat = flattenVisible(tree, expandedPaths);
      const curIdx = flat.findIndex((n) => n.path === selectedPath);
      const curNode = curIdx >= 0 ? flat[curIdx] : null;

      switch (e.key) {
        case "ArrowDown": {
          e.preventDefault();
          const next = curIdx < flat.length - 1 ? flat[curIdx + 1] : flat[0];
          if (next) {setSelectedPath(next.path);}
          break;
        }
        case "ArrowUp": {
          e.preventDefault();
          const prev = curIdx > 0 ? flat[curIdx - 1] : flat[flat.length - 1];
          if (prev) {setSelectedPath(prev.path);}
          break;
        }
        case "ArrowRight": {
          e.preventDefault();
          if (curNode && (curNode.type === "folder" || curNode.type === "object")) {
            setExpandedPaths((p) => new Set([...p, curNode.path]));
          }
          break;
        }
        case "ArrowLeft": {
          e.preventDefault();
          if (curNode && expandedPaths.has(curNode.path)) {
            setExpandedPaths((p) => {
              const n = new Set(p);
              n.delete(curNode.path);
              return n;
            });
          }
          break;
        }
        case "Enter": {
          e.preventDefault();
          if (curNode) {
            const curProtected = isSystemFile(curNode.path) || isVirtualNode(curNode);
            if (e.shiftKey || curProtected) {
              onSelect(curNode);
            } else {
              setRenamingPath(curNode.path);
            }
          }
          break;
        }
        case "F2": {
          e.preventDefault();
          if (curNode && !isSystemFile(curNode.path) && !isVirtualNode(curNode)) {
            setRenamingPath(curNode.path);
          }
          break;
        }
        case "Backspace":
        case "Delete": {
          if (curNode && !isSystemFile(curNode.path) && !isVirtualNode(curNode)) {
            e.preventDefault();
            setConfirmDelete(curNode.path);
          }
          break;
        }
        default: {
          // Cmd+key shortcuts
          if (e.metaKey || e.ctrlKey) {
            if (e.key === "c" && curNode) {
              e.preventDefault();
              void navigator.clipboard.writeText(curNode.path);
            } else if (e.key === "d" && curNode && !isSystemFile(curNode.path)) {
              e.preventDefault();
              void apiDuplicate(curNode.path).then(() => onRefresh());
            } else if (e.key === "n") {
              e.preventDefault();
              const parent = curNode
                ? curNode.type === "folder" || curNode.type === "object"
                  ? curNode.path
                  : parentPath(curNode.path)
                : "";
              if (e.shiftKey) {
                setNewItemPrompt({ kind: "folder", parentPath: parent });
              } else {
                setNewItemPrompt({ kind: "file", parentPath: parent });
              }
            }
          }
          break;
        }
      }
    },
    [tree, expandedPaths, selectedPath, renamingPath, onSelect, onRefresh],
  );

  if (tree.length === 0) {
    return (
      <div
        className="px-4 py-6 text-center text-sm"
        style={{ color: "var(--color-text-muted)" }}
        onContextMenu={handleEmptyContextMenu}
      >
        No files in workspace
        {ctxMenu && (
          <ContextMenu
            x={ctxMenu.x}
            y={ctxMenu.y}
            target={ctxMenu.target}
            onAction={handleContextMenuAction}
            onClose={() => setCtxMenu(null)}
          />
        )}
        {newItemPrompt && (
          <NewItemPrompt
            kind={newItemPrompt.kind}
            parentPath={newItemPrompt.parentPath}
            onSubmit={handleNewItemSubmit}
            onCancel={() => setNewItemPrompt(null)}
          />
        )}
      </div>
    );
  }

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragStart={handleDragStart}
      onDragOver={handleDragOver}
      onDragEnd={handleDragEnd}
      onDragCancel={handleDragCancel}
    >
      <div
        ref={containerRef}
        className="py-1 outline-none flex flex-col min-h-full"
        tabIndex={0}
        role="tree"
        onKeyDown={handleKeyDown}
        onContextMenu={handleEmptyContextMenu}
      >
        {/* ".." navigation entry for browsing up */}
        {parentDir != null && onNavigateUp && (
          <div
            role="treeitem"
            tabIndex={-1}
            onClick={onNavigateUp}
            className="w-full flex items-center gap-1.5 py-1 px-2 rounded-md text-left text-sm transition-colors duration-100 cursor-pointer select-none"
            style={{
              paddingLeft: "8px",
              color: "var(--color-text-muted)",
              borderRadius: "6px",
            }}
            onMouseEnter={(e) => {
              (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)";
            }}
            onMouseLeave={(e) => {
              (e.currentTarget as HTMLElement).style.background = "transparent";
            }}
          >
            <span className="flex-shrink-0 w-4 h-4 flex items-center justify-center">
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                <path d="m15 18-6-6 6-6" />
              </svg>
            </span>
            <span className="flex-shrink-0 flex items-center" style={{ color: "var(--color-text-muted)" }}>
              <FolderIcon />
            </span>
            <span className="truncate flex-1">..</span>
          </div>
        )}
        {tree.map((node) => (
          <DraggableNode
            key={node.path}
            node={node}
            depth={0}
            activePath={activePath}
            selectedPath={selectedPath}
            onSelect={onSelect}
            onNodeSelect={setSelectedPath}
            expandedPaths={expandedPaths}
            onToggleExpand={handleToggleExpand}
            renamingPath={renamingPath}
            onStartRename={setRenamingPath}
            onCommitRename={handleCommitRename}
            onCancelRename={handleCancelRename}
            onContextMenu={handleContextMenu}
            compact={compact}
            dragOverPath={dragOverPath}
            workspaceRoot={workspaceRoot}
          />
        ))}
        {/* Root-level drop zone: fills remaining space so items can be moved to root */}
        <RootDropZone isDragging={!!activeNode} />
      </div>

      {/* Drag overlay (ghost) — pointer-events:none so elementFromPoint sees through it */}
      <DragOverlay dropAnimation={null} style={{ pointerEvents: "none" }}>
        {activeNode ? <DragOverlayContent node={activeNode} /> : null}
      </DragOverlay>

      {/* Context menu */}
      {ctxMenu && (
        <ContextMenu
          x={ctxMenu.x}
          y={ctxMenu.y}
          target={ctxMenu.target}
          onAction={handleContextMenuAction}
          onClose={() => setCtxMenu(null)}
        />
      )}

      {/* Delete confirmation dialog */}
      {confirmDelete && (
        <ConfirmDialog
          message={`Are you sure you want to delete "${confirmDelete.split("/").pop()}"? This action cannot be undone.`}
          onConfirm={handleConfirmDelete}
          onCancel={() => setConfirmDelete(null)}
        />
      )}

      {/* New file/folder prompt */}
      {newItemPrompt && (
        <NewItemPrompt
          kind={newItemPrompt.kind}
          parentPath={newItemPrompt.parentPath}
          onSubmit={handleNewItemSubmit}
          onCancel={() => setNewItemPrompt(null)}
        />
      )}

      {/* Inject animation styles */}
      <style>{RENAME_SHAKE_STYLE}</style>
    </DndContext>
  );
}
