"use client";

import { useCallback, useEffect, useMemo, useRef, useState, type Key } from "react";
import { useReactFlow } from "@xyflow/react";
import { Button, Drawer, Empty, Input, Modal, Popover, Spin, Tree, message } from "antd";
import type { InputRef, TreeProps } from "antd";
import {
  AudioOutlined,
  FileImageOutlined,
  FolderAddOutlined,
  FolderOpenOutlined,
  FolderOutlined,
  PictureOutlined,
  PlayCircleOutlined,
  PlusOutlined,
  LeftOutlined
} from "@ant-design/icons";

import { useI18n } from "@/i18n";
import { useWorkflowStore } from "@/store/workflowStore";
import { authFetch } from "@/utils/authFetch";
import { createSmartMediaNode, detectSmartMediaKind } from "@/utils/smartMediaNode";
import {
  ASSET_LIBRARY_ITEM_DRAG_MIME,
  ASSET_LIBRARY_ITEM_KIND_DRAG_MIME_PREFIX,
  getUserFileFolderKey,
  getUserFileItemKey,
  getUserFileList,
  ROOT_PARENT_ID,
  USER_FILE_PAGE_SIZE,
  type UserFileItem,
  type UserFileListResponse,
  type UserFileMutationResponse,
} from "@/utils/userFileLibrary";
import { AssetLibraryFolderPickerModal } from "./AssetLibraryFolderPickerModal";
import { AssetLibraryItemActionsPopover } from "./AssetLibraryItemActionsPopover";
import { AssetLibraryManagerModal } from "./AssetLibraryManagerModal";

type AssetLibraryFileKind = "image" | "video" | "audio" | "other";

type AssetTreeNode = {
  key: string;
  title: string;
  item?: UserFileItem;
  isLeaf: boolean;
  depth: number;
  isCreateInput?: boolean;
  isEmpty?: boolean;
  createParentId?: number;
  children?: AssetTreeNode[];
};

interface AssetLibraryDrawerProps {
  open: boolean;
  onClose: () => void;
}

interface AssetLibraryPanelProps {
  active?: boolean;
  className?: string;
}

const treeClassName = [
  "!bg-transparent !text-[var(--text-secondary)]",
  "[&_.ant-tree-list-holder-inner]:gap-y-0.5",
  "[&_.ant-tree-treenode]:!w-full [&_.ant-tree-treenode]:!items-center [&_.ant-tree-treenode]:!p-0",
  "[&_.ant-tree-indent-unit]:!w-5",
  "[&_.ant-tree-switcher]:!hidden",
  "[&_.ant-tree-node-content-wrapper]:!m-0 [&_.ant-tree-node-content-wrapper]:!min-w-0 [&_.ant-tree-node-content-wrapper]:!flex-1 [&_.ant-tree-node-content-wrapper]:!bg-transparent [&_.ant-tree-node-content-wrapper]:!p-0 [&_.ant-tree-node-content-wrapper]:!text-inherit",
  "[&_.ant-tree-node-content-wrapper:hover]:!bg-transparent [&_.ant-tree-node-content-wrapper.ant-tree-node-selected]:!bg-transparent",
  "[&_.ant-tree-title]:!block [&_.ant-tree-title]:!w-full",
].join(" ");

function getPaneCenter() {
  const pane = document.querySelector(".react-flow");
  if (pane) {
    const rect = pane.getBoundingClientRect();
    return {
      x: rect.left + rect.width / 2,
      y: rect.top + rect.height / 2,
    };
  }
  return { x: window.innerWidth / 2, y: window.innerHeight / 2 };
}

function getFileKind(item: UserFileItem): AssetLibraryFileKind {
  if (item.fileCategory === "image") return "image";
  if (item.fileCategory === "video") return "video";
  if (item.fileCategory === "audio") return "audio";

  return detectSmartMediaKind({
    mimeType: item.mimeType,
    fileExt: item.fileExt,
    url: item.url,
  }) ?? "other";
}

function getTreeThumbUrl(item: UserFileItem): string | null {
  const kind = getFileKind(item);
  if (kind === "image") return item.url || null;
  if (kind === "video") return item.coverUrl || null;
  return null;
}

function getHoverMediaUrl(item: UserFileItem): string | null {
  const kind = getFileKind(item);
  if (kind === "image" || kind === "video" || kind === "audio") return item.url || null;
  return null;
}

function getVideoPosterUrl(item: UserFileItem): string | null {
  return item.coverUrl || null;
}

function formatCreateTime(value?: string) {
  if (!value) return "";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  const seconds = String(date.getSeconds()).padStart(2, "0");
  return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}

function MediaThumb({ item }: { item: UserFileItem }) {
  const kind = getFileKind(item);
  const thumbUrl = getTreeThumbUrl(item);

  if (kind === "image" && thumbUrl) {
    return <img src={thumbUrl} alt="" className="h-full w-full object-cover" />;
  }

  if (kind === "video") {
    return (
      <div className="relative flex h-full w-full items-center justify-center bg-black">
        {thumbUrl ? (
          <img src={thumbUrl} alt="" className="h-full w-full object-cover" />
        ) : (
          <PlayCircleOutlined className="text-[14px] text-white/70" />
        )}
        <span className="absolute inset-0 flex items-center justify-center bg-black/10">
          <PlayCircleOutlined className="text-[13px] text-white/90" />
        </span>
      </div>
    );
  }

  if (kind === "audio") {
    return <AudioOutlined className="text-[14px] text-[var(--text-secondary)]" />;
  }

  return <FileImageOutlined className="text-[14px] text-[var(--text-secondary)]" />;
}

function PreviewMedia({ item }: { item: UserFileItem }) {
  const kind = getFileKind(item);
  const mediaUrl = getHoverMediaUrl(item);

  if (kind === "image" && mediaUrl) {
    return <img src={mediaUrl} alt={item.name} className="h-full w-full object-cover" />;
  }

  if (kind === "video" && mediaUrl) {
    return (
      <video
        src={mediaUrl}
        poster={getVideoPosterUrl(item) || undefined}
        className="h-full w-full object-cover"
        muted
        autoPlay
        loop
        playsInline
        controls
      />
    );
  }

  if (kind === "audio") {
    return (
      <div className="flex h-full w-full flex-col items-center justify-center bg-[#111]">
        <div className="flex h-24 items-center gap-2">
          {[18, 42, 64, 52, 34, 46, 26, 16, 18, 12].map((height, index) => (
            <span
              key={index}
              className="w-2 rounded-full bg-neutral-400"
              style={{ height }}
            />
          ))}
        </div>
        <AudioOutlined className="mt-3 text-[18px] text-neutral-300" />
        {mediaUrl && (
          <audio
            src={mediaUrl}
            controls
            className="mt-5 w-[220px]"
          />
        )}
      </div>
    );
  }

  return (
    <div className="flex h-full w-full items-center justify-center bg-[#111] text-[var(--text-disabled)]">
      <FileImageOutlined className="text-[28px]" />
    </div>
  );
}

function AssetPreviewCard({
  item,
  applying,
  canApply,
  onApply,
  t,
}: {
  item: UserFileItem;
  applying: boolean;
  canApply: boolean;
  onApply: () => void;
  t: ReturnType<typeof useI18n>["t"];
}) {
  const kind = getFileKind(item);

  return (
    <div className="nodrag nopan nowheel w-[280px] overflow-hidden rounded-xl border border-[var(--border-subtle)] bg-[var(--surface-2)] text-[var(--text-secondary)] shadow-[var(--shadow-panel)]">
      <div className="h-[226px] overflow-hidden rounded-t-[10px] bg-[var(--surface-1)]">
        <PreviewMedia item={item} />
      </div>
      <div className="bg-[var(--surface-3)] px-2.5 py-2">
        <div className="flex items-center justify-between gap-2">
          <span className="min-w-0 truncate text-sm font-semibold text-[var(--text-primary)]">{item.name}</span>
          {kind === "image" ? (
            <PictureOutlined className="text-[var(--text-muted)]" />
          ) : kind === "video" ? (
            <PlayCircleOutlined className="text-[var(--text-muted)]" />
          ) : (
            <AudioOutlined className="text-[var(--text-muted)]" />
          )}
        </div>
        <div className="mt-1 truncate text-xs text-[var(--text-muted)]">
          {t("assetLibrary.createdAt")} {formatCreateTime(item.createTime)}
        </div>
        <Button
          block
          disabled={!canApply}
          loading={applying}
          onClick={onApply}
          className="mt-2 !h-10 !rounded-md !border-[var(--border-strong)] !bg-[var(--surface-active)] !text-sm !font-semibold !text-[var(--text-primary)] hover:!border-[var(--border-strong)] hover:!bg-[var(--surface-hover)]"
        >
          {t("assetLibrary.applyToCanvas")}
        </Button>
      </div>
    </div>
  );
}

export function AssetLibraryPanel({ active = true, className = "" }: AssetLibraryPanelProps) {
  const { t } = useI18n();
  const addNode = useWorkflowStore((state) => state.addNode);
  const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
  const selectSingleNode = useWorkflowStore((state) => state.selectSingleNode);
  const { screenToFlowPosition } = useReactFlow();
  const [folders, setFolders] = useState<UserFileItem[]>([]);
  const [childrenByFolderId, setChildrenByFolderId] = useState<Record<number, UserFileItem[]>>({});
  const [expandedKeys, setExpandedKeys] = useState<Key[]>([]);
  const [loadingRoot, setLoadingRoot] = useState(false);
  const [loadingFolderIds, setLoadingFolderIds] = useState<Set<number>>(() => new Set());
  const [loadedFolderIds, setLoadedFolderIds] = useState<Set<number>>(() => new Set());
  const [error, setError] = useState<string | null>(null);
  const [selectedFile, setSelectedFile] = useState<UserFileItem | null>(null);
  const [hoveredFileId, setHoveredFileId] = useState<number | null>(null);
  const [applying, setApplying] = useState(false);
  const [addPopoverOpen, setAddPopoverOpen] = useState(false);
  const [creatingFolderParentId, setCreatingFolderParentId] = useState<number | null>(null);
  const [newFolderName, setNewFolderName] = useState("");
  const [submittingFolder, setSubmittingFolder] = useState(false);
  const [managerModalOpen, setManagerModalOpen] = useState(false);
  const [actionItemKey, setActionItemKey] = useState<string | null>(null);
  const [renamingItemKey, setRenamingItemKey] = useState<string | null>(null);
  const [renameValue, setRenameValue] = useState("");
  const [submittingRename, setSubmittingRename] = useState(false);
  const [movingItem, setMovingItem] = useState<UserFileItem | null>(null);
  const [submittingMove, setSubmittingMove] = useState(false);
  const newFolderInputRef = useRef<InputRef>(null);
  const renameInputRef = useRef<InputRef>(null);
  const folderCommitInFlightRef = useRef(false);
  const renameCommitInFlightRef = useRef(false);

  const fetchUserFiles = useCallback(async (parentId: number, signal?: AbortSignal) => {
    const response = await authFetch(`/api/popi/user-file/list?parentId=${parentId}&page=1&pageSize=${USER_FILE_PAGE_SIZE}`, {
      signal,
    });
    const payload = (await response.json().catch(() => null)) as UserFileListResponse | null;
    if (!response.ok || !payload?.success) {
      throw new Error(payload?.error || t("assetLibrary.loadFailed"));
    }
    return getUserFileList(payload.data);
  }, [t]);

  const loadRootFolders = useCallback(async (signal?: AbortSignal) => {
    setLoadingRoot(true);
    setError(null);
    try {
      const list = await fetchUserFiles(ROOT_PARENT_ID, signal);
      setFolders(list.filter((item) => item.nodeType === "folder"));
    } catch (err) {
      if (err instanceof DOMException && err.name === "AbortError") return;
      setError(err instanceof Error ? err.message : t("assetLibrary.loadFailed"));
      setFolders([]);
    } finally {
      if (!signal?.aborted) setLoadingRoot(false);
    }
  }, [fetchUserFiles, t]);

  const loadFolderChildren = useCallback(async (folderId: number, force = false) => {
    if (!force && (loadedFolderIds.has(folderId) || loadingFolderIds.has(folderId))) return;

    setLoadingFolderIds((current) => new Set(current).add(folderId));
    setError(null);
    try {
      const list = await fetchUserFiles(folderId);
      setChildrenByFolderId((current) => ({
        ...current,
        [folderId]: list.filter((item) => item.nodeType === "folder" || getFileKind(item) !== "other"),
      }));
      setLoadedFolderIds((current) => new Set(current).add(folderId));
    } catch (err) {
      setError(err instanceof Error ? err.message : t("assetLibrary.loadFailed"));
    } finally {
      setLoadingFolderIds((current) => {
        const next = new Set(current);
        next.delete(folderId);
        return next;
      });
    }
  }, [fetchUserFiles, loadedFolderIds, loadingFolderIds, t]);

  useEffect(() => {
    if (!active) return;
    const controller = new AbortController();
    void loadRootFolders(controller.signal);
    return () => controller.abort();
  }, [active, loadRootFolders]);

  useEffect(() => {
    if (creatingFolderParentId === null) return;
    window.setTimeout(() => newFolderInputRef.current?.focus(), 0);
  }, [creatingFolderParentId]);

  useEffect(() => {
    if (renamingItemKey === null) return;
    window.setTimeout(() => renameInputRef.current?.focus(), 0);
  }, [renamingItemKey]);

  const buildTreeNodes = useCallback((items: UserFileItem[], depth: number): AssetTreeNode[] => {
    return items.map((item) => {
      const isFolder = item.nodeType === "folder";
      const itemChildren = isFolder ? childrenByFolderId[item.id] || [] : [];
      const children = isFolder ? buildTreeNodes(itemChildren, depth + 1) : undefined;
      const isLoadedEmpty = isFolder
        && loadedFolderIds.has(item.id)
        && !loadingFolderIds.has(item.id)
        && itemChildren.length === 0;
      const emptyChildren = isLoadedEmpty
        ? [{
            key: `${getUserFileFolderKey(item.id)}-empty`,
            title: "",
            isLeaf: true,
            depth: depth + 1,
            isEmpty: true,
          }]
        : children;
      const nextChildren = isFolder && creatingFolderParentId === item.id
        ? [{
            key: `create-folder-${item.id}`,
            title: "",
            isLeaf: true,
            depth: depth + 1,
            isCreateInput: true,
            createParentId: item.id,
          }, ...(itemChildren.length === 0 ? [] : (children || []))]
        : emptyChildren;

      return {
        key: getUserFileItemKey(item),
        title: item.name,
        item,
        isLeaf: !isFolder,
        depth,
        children: nextChildren,
      };
    });
  }, [childrenByFolderId, creatingFolderParentId, loadedFolderIds, loadingFolderIds]);

  const treeData = useMemo<AssetTreeNode[]>(() => buildTreeNodes(folders, 0), [buildTreeNodes, folders]);

  const disabledMoveFolderIds = useMemo(() => {
    if (!movingItem || movingItem.nodeType !== "folder") return undefined;

    const disabledIds = new Set<number>([movingItem.id]);
    const collectDescendantFolderIds = (folderId: number) => {
      const children = childrenByFolderId[folderId] || [];
      children.forEach((child) => {
        if (child.nodeType !== "folder" || disabledIds.has(child.id)) return;
        disabledIds.add(child.id);
        collectDescendantFolderIds(child.id);
      });
    };
    collectDescendantFolderIds(movingItem.id);
    return disabledIds;
  }, [childrenByFolderId, movingItem]);

  const handleExpand: TreeProps<AssetTreeNode>["onExpand"] = (keys, info) => {
    setExpandedKeys(keys);
    const item = info.node.item;
    if (item?.nodeType === "folder" && info.expanded) {
      void loadFolderChildren(item.id);
    }
  };

  const toggleFolderExpansion = useCallback((node: AssetTreeNode) => {
    const item = node.item;
    if (!item || item.nodeType !== "folder") return;

    setExpandedKeys((current) => {
      const isExpanded = current.includes(node.key);
      if (isExpanded) {
        return current.filter((key) => key !== node.key);
      }

      void loadFolderChildren(item.id);
      return [...current, node.key];
    });
  }, [loadFolderChildren]);

  const refreshFolderChildren = useCallback(async (folderId: number) => {
    setLoadedFolderIds((current) => {
      const next = new Set(current);
      next.delete(folderId);
      return next;
    });
    setChildrenByFolderId((current) => {
      const next = { ...current };
      delete next[folderId];
      return next;
    });
    await loadFolderChildren(folderId, true);
  }, [loadFolderChildren]);

  const clearFolderCache = useCallback((folderId: number) => {
    setLoadedFolderIds((current) => {
      const next = new Set(current);
      next.delete(folderId);
      return next;
    });
    setChildrenByFolderId((current) => {
      const next = { ...current };
      delete next[folderId];
      return next;
    });
    setExpandedKeys((current) => current.filter((key) => key !== getUserFileFolderKey(folderId)));
    setSelectedFile((current) => current?.parentId === folderId ? null : current);
  }, []);

  const removeFileFromFolder = useCallback((file: UserFileItem) => {
    setChildrenByFolderId((current) => {
      const files = current[file.parentId];
      if (!files) return current;
      return {
        ...current,
        [file.parentId]: files.filter((item) => item.id !== file.id),
      };
    });
    setSelectedFile((current) => current?.id === file.id ? null : current);
    setHoveredFileId((current) => current === file.id ? null : current);
  }, []);

  const renameItemLocally = useCallback((item: UserFileItem, name: string) => {
    if (item.parentId === ROOT_PARENT_ID) {
      setFolders((current) => current.map((folder) => (
        folder.id === item.id ? { ...folder, name } : folder
      )));
    } else {
      setChildrenByFolderId((current) => {
        const children = current[item.parentId];
        if (!children) return current;
        return {
          ...current,
          [item.parentId]: children.map((child) => (
            child.id === item.id ? { ...child, name } : child
          )),
        };
      });
    }

    setSelectedFile((current) => current?.id === item.id ? { ...current, name } : current);
  }, []);

  const handleStartCreateFolder = useCallback((parentId = ROOT_PARENT_ID) => {
    setAddPopoverOpen(false);
    setActionItemKey(null);
    setError(null);
    setNewFolderName("");
    setCreatingFolderParentId(parentId);
    if (parentId !== ROOT_PARENT_ID) {
      const key = getUserFileFolderKey(parentId);
      setExpandedKeys((current) => current.includes(key) ? current : [...current, key]);
      void loadFolderChildren(parentId);
    }
  }, [loadFolderChildren]);

  const handleCommitNewFolder = useCallback(async (parentId: number) => {
    if (folderCommitInFlightRef.current) return;
    const name = newFolderName.trim();
    if (!name) {
      setCreatingFolderParentId(null);
      setNewFolderName("");
      return;
    }

    folderCommitInFlightRef.current = true;
    setSubmittingFolder(true);
    setError(null);
    try {
      const response = await authFetch("/api/popi/user-file/create-folder", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ parentId, name }),
      });
      const payload = (await response.json().catch(() => null)) as UserFileMutationResponse | null;
      if (!response.ok || !payload?.success) {
        throw new Error(payload?.error || t("assetLibrary.createFolderFailed"));
      }
      setCreatingFolderParentId(null);
      setNewFolderName("");
      if (parentId === ROOT_PARENT_ID) {
        await loadRootFolders();
      } else {
        await refreshFolderChildren(parentId);
        setExpandedKeys((current) => {
          const key = getUserFileFolderKey(parentId);
          return current.includes(key) ? current : [...current, key];
        });
      }
    } catch (err) {
      const messageText = err instanceof Error ? err.message : t("assetLibrary.createFolderFailed");
      setError(messageText);
      void message.error(messageText);
    } finally {
      folderCommitInFlightRef.current = false;
      setSubmittingFolder(false);
    }
  }, [loadRootFolders, newFolderName, refreshFolderChildren, t]);

  const handleOpenManagerModal = useCallback(() => {
    setAddPopoverOpen(false);
    setError(null);
    setManagerModalOpen(true);
  }, []);

  const handleUploadedAsset = useCallback(async (folderId: number) => {
    await refreshFolderChildren(folderId);
    setExpandedKeys((current) => {
      const key = getUserFileFolderKey(folderId);
      return current.includes(key) ? current : [...current, key];
    });
  }, [refreshFolderChildren]);

  const handleManagerChanged = useCallback(async (folderId: number) => {
    if (folderId === ROOT_PARENT_ID) {
      await loadRootFolders();
      return;
    }
    await handleUploadedAsset(folderId);
  }, [handleUploadedAsset, loadRootFolders]);

  const handleStartRenameItem = useCallback((item: UserFileItem) => {
    setActionItemKey(null);
    setHoveredFileId(null);
    setError(null);
    setRenamingItemKey(getUserFileItemKey(item));
    setRenameValue(item.name);
  }, []);

  const handleStartMoveItem = useCallback((item: UserFileItem) => {
    setActionItemKey(null);
    setHoveredFileId(null);
    setError(null);
    setMovingItem(item);
  }, []);

  const refreshAfterMove = useCallback(async (sourceParentId: number, targetParentId: number) => {
    if (sourceParentId === ROOT_PARENT_ID || targetParentId === ROOT_PARENT_ID) {
      await loadRootFolders();
    }
    if (sourceParentId !== ROOT_PARENT_ID) {
      await refreshFolderChildren(sourceParentId);
    }
    if (targetParentId !== ROOT_PARENT_ID && targetParentId !== sourceParentId) {
      await refreshFolderChildren(targetParentId);
    }
  }, [loadRootFolders, refreshFolderChildren]);

  const handleConfirmMove = useCallback(async (targetId: number) => {
    if (!movingItem) return;
    if (movingItem.parentId === targetId) {
      setMovingItem(null);
      return;
    }
    if (disabledMoveFolderIds?.has(targetId)) return;

    setSubmittingMove(true);
    setError(null);
    try {
      const response = await authFetch("/api/popi/user-file/move", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          id: movingItem.id,
          targetId,
        }),
      });
      const payload = (await response.json().catch(() => null)) as UserFileMutationResponse | null;
      if (!response.ok || !payload?.success) {
        throw new Error(payload?.error || t("assetLibrary.moveFailed"));
      }

      await refreshAfterMove(movingItem.parentId, targetId);
      setMovingItem(null);
      setSelectedFile((current) => current?.id === movingItem.id ? null : current);
      void message.success(t("assetLibrary.moveSuccess"));
    } catch (err) {
      const messageText = err instanceof Error ? err.message : t("assetLibrary.moveFailed");
      setError(messageText);
      void message.error(messageText);
    } finally {
      setSubmittingMove(false);
    }
  }, [disabledMoveFolderIds, movingItem, refreshAfterMove, t]);

  const handleCancelRename = useCallback(() => {
    setRenamingItemKey(null);
    setRenameValue("");
  }, []);

  const handleCommitRename = useCallback(async (item: UserFileItem) => {
    if (renameCommitInFlightRef.current) return;
    const name = renameValue.trim();
    if (!name || name === item.name) {
      handleCancelRename();
      return;
    }

    renameCommitInFlightRef.current = true;
    setSubmittingRename(true);
    setError(null);
    try {
      const response = await authFetch("/api/popi/user-file/rename", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ id: item.id, name }),
      });
      const payload = (await response.json().catch(() => null)) as UserFileMutationResponse | null;
      if (!response.ok || !payload?.success) {
        throw new Error(payload?.error || t("assetLibrary.renameFailed"));
      }
      handleCancelRename();
      renameItemLocally(item, name);
    } catch (err) {
      const messageText = err instanceof Error ? err.message : t("assetLibrary.renameFailed");
      setError(messageText);
      void message.error(messageText);
    } finally {
      renameCommitInFlightRef.current = false;
      setSubmittingRename(false);
    }
  }, [handleCancelRename, renameItemLocally, renameValue, t]);

  const handleDeleteItem = useCallback((item: UserFileItem) => {
    const isFolder = item.nodeType === "folder";
    setActionItemKey(null);
    setHoveredFileId(null);
    Modal.confirm({
      title: isFolder ? t("assetLibrary.deleteFolderTitle") : t("assetLibrary.deleteAssetTitle"),
      content: isFolder ? t("assetLibrary.deleteFolderConfirm") : t("assetLibrary.deleteAssetConfirm"),
      okText: t("assetLibrary.delete"),
      cancelText: t("common.cancel"),
      okButtonProps: { danger: true },
      centered: true,
      async onOk() {
        setError(null);
        try {
          const response = await authFetch(`/api/popi/user-file/delete?id=${item.id}`);
          const payload = (await response.json().catch(() => null)) as UserFileMutationResponse | null;
          if (!response.ok || !payload?.success) {
            throw new Error(payload?.error || t("assetLibrary.deleteFailed"));
          }
          if (isFolder) {
            clearFolderCache(item.id);
            if (item.parentId === ROOT_PARENT_ID) {
              await loadRootFolders();
            } else {
              await refreshFolderChildren(item.parentId);
            }
          } else {
            removeFileFromFolder(item);
            await refreshFolderChildren(item.parentId);
          }
        } catch (err) {
          const messageText = err instanceof Error ? err.message : t("assetLibrary.deleteFailed");
          setError(messageText);
          void message.error(messageText);
          throw err;
        }
      },
    });
  }, [clearFolderCache, loadRootFolders, refreshFolderChildren, removeFileFromFolder, t]);

  const addMenu = (
    <div className="nodrag nopan nowheel flex min-w-[176px] flex-col rounded-lg bg-[var(--surface-2)] p-1 text-sm text-[var(--text-secondary)] shadow-[var(--shadow-panel)]">
      <button
        type="button"
        className="flex h-9 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
        onClick={handleOpenManagerModal}
      >
        <PictureOutlined className="text-[14px]" />
        <span>{t("assetLibrary.manage")}</span>
      </button>
      <button
        type="button"
        className="flex h-9 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
        onClick={() => handleStartCreateFolder(ROOT_PARENT_ID)}
      >
        <FolderAddOutlined className="text-[14px]" />
        <span>{t("assetLibrary.newFolder")}</span>
      </button>
    </div>
  );

  const handleApplyToCanvas = useCallback(async (file: UserFileItem) => {
    if (!file.url || applying) return;
    const kind = getFileKind(file);
    if (kind === "other") return;

    setApplying(true);
    try {
      const center = getPaneCenter();
      const position = screenToFlowPosition({
        x: center.x + Math.random() * 100 - 50,
        y: center.y + Math.random() * 100 - 50,
      });

      await createSmartMediaNode({
        source: {
          kind,
          url: file.url,
          filename: file.name || `${t("assetLibrary.asset")} ${file.id}`,
          mimeType: file.mimeType,
          previewUrl: kind === "video" ? getVideoPosterUrl(file) : kind === "image" ? file.coverUrl || file.url : null,
          assetId: kind === "image" ? file.mediaId : file.id,
          resolver: kind === "image" && file.mediaId
            ? {
                type: "popi-media",
                id: file.mediaId,
                name: file.name,
                url: file.url,
                thumbUrl: file.coverUrl,
              }
            : null,
        },
        position,
        addNode,
        updateNodeData,
        getNodeData: (nodeId) => useWorkflowStore.getState().getNodeById(nodeId)?.data,
        selectSingleNode,
      });
    } finally {
      setApplying(false);
    }
  }, [addNode, applying, screenToFlowPosition, selectSingleNode, t, updateNodeData]);

  const titleRender = useCallback((node: AssetTreeNode) => {
    if (node.isEmpty) {
      return (
        <div className="flex h-8 items-center px-3 text-xs text-[var(--text-disabled)]">
          {t("assetLibrary.folderEmpty")}
        </div>
      );
    }

    if (node.isCreateInput) {
      return (
        <div className="text-[var(--text-secondary)]">
          <Input
            ref={newFolderInputRef}
            value={newFolderName}
            disabled={submittingFolder}
            onClick={(event) => event.stopPropagation()}
            onChange={(event) => setNewFolderName(event.target.value)}
            onBlur={() => void handleCommitNewFolder(node.createParentId ?? ROOT_PARENT_ID)}
            onPressEnter={() => void handleCommitNewFolder(node.createParentId ?? ROOT_PARENT_ID)}
            onKeyDown={(event) => {
              event.stopPropagation();
              if (event.key === "Escape") {
                setCreatingFolderParentId(null);
                setNewFolderName("");
              }
            }}
            placeholder={t("assetLibrary.newFolderPlaceholder")}
            className="!h-8 !rounded-lg !border-[var(--border-subtle)] !bg-[var(--surface-1)] !text-sm [&_input]:!text-[var(--text-secondary)] [&_input::placeholder]:!text-[var(--text-muted)]"
          />
        </div>
      );
    }

    const item = node.item;
    if (!item) return null;
    const itemKey = getUserFileItemKey(item);
    if (item.nodeType === "folder") {
      const isExpanded = expandedKeys.includes(node.key);
      const isRenaming = renamingItemKey === itemKey;
      if (isRenaming) {
        return (
          <Input
            ref={renameInputRef}
            value={renameValue}
            disabled={submittingRename}
            onChange={(event) => setRenameValue(event.target.value)}
            onBlur={() => void handleCommitRename(item)}
            onPressEnter={() => void handleCommitRename(item)}
            onKeyDown={(event) => {
              if (event.key === "Escape") {
                event.stopPropagation();
                handleCancelRename();
              }
            }}
            className="!h-8 !rounded-lg !border-[var(--border-subtle)] !bg-[var(--surface-1)] !text-sm [&_input]:!text-[var(--text-secondary)]"
          />
        );
      }

      return (
        <div
          role="button"
          tabIndex={0}
          className="group flex h-8 w-full min-w-0 cursor-pointer items-center gap-2 rounded-md border-0 bg-transparent py-0 pr-2 pl-1 text-left text-sm font-semibold text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
          onClick={() => toggleFolderExpansion(node)}
          onKeyDown={(event) => {
            if (event.key === "Enter" || event.key === " ") {
              event.preventDefault();
              toggleFolderExpansion(node);
            }
          }}
        >
          <span className="flex h-6 w-6 shrink-0 items-center justify-center">
            {isExpanded ? <FolderOpenOutlined /> : <FolderOutlined />}
          </span>
          <span className="min-w-0 flex-1 truncate">{item.name}</span>
          {loadingFolderIds.has(item.id) && <Spin size="small" className="ml-auto" />}
          {!loadingFolderIds.has(item.id) && (
            <AssetLibraryItemActionsPopover
              open={actionItemKey === itemKey}
              onOpenChange={(nextOpen) => setActionItemKey(nextOpen ? itemKey : null)}
              ariaLabel={t("assetLibrary.itemActions")}
              showCreateFolder={node.depth === 0}
              onCreateFolder={() => handleStartCreateFolder(item.id)}
              onMove={() => handleStartMoveItem(item)}
              onRename={() => handleStartRenameItem(item)}
              onDelete={() => handleDeleteItem(item)}
            />
          )}
        </div>
      );
    }

    const isRenaming = renamingItemKey === itemKey;
    if (isRenaming) {
      return (
        <Input
          ref={renameInputRef}
          value={renameValue}
          disabled={submittingRename}
          onChange={(event) => setRenameValue(event.target.value)}
          onBlur={() => void handleCommitRename(item)}
          onPressEnter={() => void handleCommitRename(item)}
          onKeyDown={(event) => {
            if (event.key === "Escape") {
              event.stopPropagation();
              handleCancelRename();
            }
          }}
          className="!h-8 !rounded-lg !border-[var(--border-subtle)] !bg-[var(--surface-1)] !text-sm [&_input]:!text-[var(--text-secondary)]"
        />
      );
    }

    const isSelected = selectedFile?.id === item.id;
    const isHovered = hoveredFileId === item.id;
    const isActionOpen = actionItemKey === itemKey;
    const isActive = isSelected || isHovered || isActionOpen;
    const kind = getFileKind(item);
    const canApplyItem = kind !== "other" && Boolean(item.url);
    const row = (
      <div
        role="button"
        tabIndex={0}
        draggable={canApplyItem}
        className={`group flex h-9 w-full min-w-0 cursor-pointer items-center gap-2 rounded-md border-0 py-0.5 pr-2 pl-1 text-sm transition-colors ${
          isActive
            ? "bg-[var(--surface-active)] text-[var(--text-primary)]"
            : "bg-transparent text-[var(--text-muted)] hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
        }`}
        onClick={() => setSelectedFile(item)}
        onDragStart={(event) => {
          if (!canApplyItem || !item.url) {
            event.preventDefault();
            return;
          }
          event.stopPropagation();
          event.dataTransfer.effectAllowed = "copy";
          event.dataTransfer.setData(ASSET_LIBRARY_ITEM_DRAG_MIME, JSON.stringify(item));
          event.dataTransfer.setData(`${ASSET_LIBRARY_ITEM_KIND_DRAG_MIME_PREFIX}${kind}`, "1");
          event.dataTransfer.setData("text/plain", item.name);
          setSelectedFile(item);
        }}
        onDragEnd={() => setHoveredFileId(null)}
        onKeyDown={(event) => {
          if (event.key === "Enter" || event.key === " ") {
            event.preventDefault();
            setSelectedFile(item);
          }
        }}
      >
        <span className="flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded bg-[var(--surface-3)]">
          <MediaThumb item={item} />
        </span>
        <span className="min-w-0 flex-1 truncate text-left">{item.name}</span>
        <AssetLibraryItemActionsPopover
          open={isActionOpen}
          onOpenChange={(nextOpen) => {
            setActionItemKey(nextOpen ? itemKey : null);
            if (nextOpen) setHoveredFileId(null);
          }}
          ariaLabel={t("assetLibrary.itemActions")}
          onMove={() => handleStartMoveItem(item)}
          onRename={() => handleStartRenameItem(item)}
          onDelete={() => handleDeleteItem(item)}
        />
      </div>
    );

    return (
      <Popover
        open={isHovered && !isActionOpen}
        onOpenChange={(nextOpen) => {
          if (actionItemKey !== null) return;
          setHoveredFileId(nextOpen ? item.id : null);
        }}
        content={(
          <AssetPreviewCard
            item={item}
            applying={applying && isHovered}
            canApply={canApplyItem}
            onApply={() => handleApplyToCanvas(item)}
            t={t}
          />
        )}
        trigger="hover"
        placement="right"
        align={{ offset: [20, 0] }}
        arrow={false}
        mouseEnterDelay={0.08}
        mouseLeaveDelay={0.12}
        getPopupContainer={() => document.body}
        styles={{
          container: { padding: 0 },
          content: {
            padding: 0,
            background: "transparent",
            boxShadow: "none",
          },
        }}
      >
        <div className="flex w-full min-w-0">{row}</div>
      </Popover>
    );
  }, [
    actionItemKey,
    applying,
    expandedKeys,
    handleApplyToCanvas,
    handleCancelRename,
    handleCommitRename,
    handleCommitNewFolder,
    handleDeleteItem,
    handleStartCreateFolder,
    handleStartMoveItem,
    handleStartRenameItem,
    hoveredFileId,
    loadingFolderIds,
    renameValue,
    renamingItemKey,
    selectedFile,
    newFolderName,
    submittingRename,
    submittingFolder,
    t,
    toggleFolderExpansion,
  ]);

  return (
    <>
      <div className={`flex h-full min-h-0 flex-col bg-[var(--surface-1)] text-[var(--text-primary)] ${className}`}>
        <div className="flex items-center justify-between gap-3 px-4 pt-4">
          <div className="text-xs text-[var(--text-muted)]">{t("assetLibrary.folders")}</div>
          <Popover
            open={addPopoverOpen}
            onOpenChange={setAddPopoverOpen}
            content={addMenu}
            trigger="click"
            placement="bottomRight"
            arrow={false}
            getPopupContainer={() => document.body}
            styles={{
              container: {
                padding: 0,
              },
              content: {
                padding: 0,
                background: "transparent",
                boxShadow: "none",
              },
            }}
          >
            <Button
              type="text"
              shape="default"
              icon={<PlusOutlined />}
              aria-label={t("assetLibrary.add")}
              className="!flex !h-8 !w-8 !items-center !justify-center !rounded-lg !bg-[var(--surface-3)] !text-[var(--text-primary)] hover:!bg-[var(--surface-hover)]"
            />
          </Popover>
        </div>

        <div className="px-4 pt-2">
          {creatingFolderParentId === ROOT_PARENT_ID && (
            <Input
              ref={newFolderInputRef}
              value={newFolderName}
              disabled={submittingFolder}
              onChange={(event) => setNewFolderName(event.target.value)}
              onBlur={() => void handleCommitNewFolder(ROOT_PARENT_ID)}
              onPressEnter={() => void handleCommitNewFolder(ROOT_PARENT_ID)}
              placeholder={t("assetLibrary.newFolderPlaceholder")}
              className="mb-2 !h-8 !rounded-lg !border-[var(--border-subtle)] !bg-[var(--surface-1)] !text-sm [&_input]:!text-[var(--text-secondary)] [&_input::placeholder]:!text-[var(--text-muted)]"
            />
          )}
        </div>

        <div className="min-h-0 flex-1 overflow-y-auto px-4 pb-5">
          {loadingRoot ? (
            <div className="flex h-40 items-center justify-center">
              <Spin />
            </div>
          ) : error ? (
            <div className="flex h-40 flex-col items-center justify-center gap-3 text-xs text-[var(--text-muted)]">
              <span>{error}</span>
              <Button size="small" onClick={() => void loadRootFolders()}>
                {t("assetLibrary.retry")}
              </Button>
            </div>
          ) : treeData.length === 0 ? (
            <Empty
              image={Empty.PRESENTED_IMAGE_SIMPLE}
              description={<span className="text-xs text-[var(--text-disabled)]">{t("assetLibrary.empty")}</span>}
            />
          ) : (
            <Tree<AssetTreeNode>
              blockNode
              showIcon={false}
              showLine={false}
              className={treeClassName}
              treeData={treeData}
              expandedKeys={expandedKeys}
              selectedKeys={selectedFile ? [`file-${selectedFile.id}`] : []}
              onExpand={handleExpand}
              titleRender={titleRender}
            />
          )}
        </div>
      </div>
      <AssetLibraryManagerModal
        open={managerModalOpen}
        onClose={() => setManagerModalOpen(false)}
        onChanged={handleManagerChanged}
      />
      <AssetLibraryFolderPickerModal
        open={movingItem !== null}
        title={t("assetLibrary.moveTitle")}
        confirmText={t("assetLibrary.move")}
        folders={folders}
        childrenByFolderId={childrenByFolderId}
        loadingFolderIds={loadingFolderIds}
        loadedFolderIds={loadedFolderIds}
        initialFolderId={movingItem?.parentId === ROOT_PARENT_ID ? null : movingItem?.parentId ?? null}
        disabledFolderIds={disabledMoveFolderIds}
        confirmLoading={submittingMove}
        onLoadFolderChildren={loadFolderChildren}
        onClose={() => {
          if (!submittingMove) setMovingItem(null);
        }}
        onConfirm={handleConfirmMove}
      />
    </>
  );
}

export function AssetLibraryDrawer({ open, onClose }: AssetLibraryDrawerProps) {
  const { t } = useI18n();

  return (
    <Drawer
      open={open}
      onClose={onClose}
      placement="left"
      mask={false}
      closable={false}
      zIndex={60}
      rootClassName="nodrag nopan nowheel w-[352px]"
      styles={{
        section: {
          overflow: "visible",
          borderRight: "1px solid var(--border-subtle)",
          borderRadius: "0 10px 10px 0",
          background: "var(--surface-1)",
          boxShadow: "none",
        },
        body: {
          padding: 0,
          overflow: "visible",
        },
      }}
    >
      <div className="flex h-full min-h-0 flex-col bg-[var(--surface-1)] text-[var(--text-primary)]">
        <div className="flex h-[52px] items-center gap-2 px-4 pt-3">
          <button
            type="button"
            onClick={onClose}
            aria-label={t("common.close")}
            className="flex h-7 w-7 items-center justify-center rounded-md text-[var(--text-muted)] transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]"
          >
            <LeftOutlined />
          </button>
          <h2 className="text-[22px] font-semibold leading-7 text-[var(--text-primary)]">{t("assetLibrary.title")}</h2>
        </div>
        <AssetLibraryPanel active={open} />
      </div>
    </Drawer>
  );
}
