// MNote debug /tree shell filetree drag/drop and preflight runtime.

import {
  getFileTreeRowDocumentId,
} from "./tree-shell-filetree-runtime.js";
import { normalizeTreeShellText as normalizeText } from "./tree-shell-state-runtime.js";

export const TREE_SHELL_FILETREE_DRAG_MIME = "application/x-mnote-filetree-row-ids";

export function createTreeShellFileTreeDndRuntime(context) {
  let activeFileTreeDropRowId = null;
  let activeFileTreeDropPosition = null;
  let activeFileTreeRootDrop = false;
  let fileTreeHoverExpandTimer = 0;
  let activeFileTreePreflightElement = null;

  const getFileTreeDropTargetFromElement = (element) => {
    const row = element instanceof Element
      ? element.closest('.tree-row[data-shell-mode="filetree"]')
      : null;
    if (!(row instanceof HTMLElement)) {
      return {
        rowId: null,
        rowKind: "root",
        nodeId: null,
        documentId: null,
        ownerDocumentId: null,
        assetId: null,
      };
    }
    return {
      rowId: normalizeText(row.dataset.rowId) || null,
      rowKind: normalizeText(row.dataset.rowKind, "document"),
      nodeId: normalizeText(row.dataset.nodeId) || null,
      documentId: normalizeText(row.dataset.documentId) || null,
      ownerDocumentId: normalizeText(row.dataset.ownerDocumentId) || null,
      assetId: normalizeText(row.dataset.assetId) || null,
    };
  };

  const getFileTreeDropTargetFromEvent = (event) => {
    const target = getFileTreeDropTargetFromElement(event.target);
    if (!target.rowId) return { ...target, dropPosition: "inside" };
    const row = event.target instanceof Element
      ? event.target.closest('.tree-row[data-shell-mode="filetree"]')
      : null;
    if (!(row instanceof HTMLElement)) return { ...target, dropPosition: "inside" };
    const rect = row.getBoundingClientRect();
    const offset = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;
    const item = context.getFileTreeRowById().get(target.rowId);
    if (offset < 0.25) return { ...target, dropPosition: "before" };
    if (offset > 0.75) return { ...target, dropPosition: "after" };
    return {
      ...target,
      dropPosition: item && context.canExpandFileTreeRow(item) ? "inside" : "after",
    };
  };

  const resolveLocalFileTreeParentId = (target) => {
    if (!target || target.rowKind === "root") return null;
    const targetItem = target.rowId ? context.getFileTreeRowById().get(target.rowId) : null;
    if (targetItem && targetItem.rowKind === "folder") {
      return targetItem.nodeId;
    }
    if (targetItem && targetItem.parentNodeId) {
      return targetItem.parentNodeId;
    }
    return null;
  };

  const isFileTreeDescendantOf = (candidate, ancestor) => {
    let cursor = candidate;
    while (cursor && cursor.parentNodeId) {
      if (cursor.parentNodeId === ancestor.nodeId) return true;
      cursor = context.getItemById().get(cursor.parentNodeId) || null;
    }
    return false;
  };

  const validateFileTreeInternalDrop = (target, rowIds, copy) => {
    const sourceRowIds = Array.isArray(rowIds) ? rowIds.filter(Boolean) : [];
    if (sourceRowIds.length === 0) return { ok: false, reason: "empty" };
    const fileTreeRowById = context.getFileTreeRowById();
    const targetItem = target?.rowId ? fileTreeRowById.get(target.rowId) : null;
    if (targetItem && targetItem.capabilities.some((capability) =>
      capability === "readonly" || capability === "readOnly" || capability === "permissionDenied"
    )) {
      return { ok: false, reason: "readonly" };
    }
    for (const rowId of sourceRowIds) {
      const sourceItem = fileTreeRowById.get(rowId);
      if (!sourceItem) return { ok: false, reason: "cross_workspace" };
      if (targetItem && targetItem.rowId === sourceItem.rowId) {
        return { ok: false, reason: "self" };
      }
      if (targetItem && sourceItem.rowKind === "folder" && isFileTreeDescendantOf(targetItem, sourceItem)) {
        return { ok: false, reason: "descendant" };
      }
      if (copy && sourceItem.rowKind === "folder") {
        return { ok: false, reason: "copy_folder_unsupported" };
      }
    }
    return { ok: true, reason: "" };
  };

  const filterRedundantFileTreeRowIds = (rowIds) => {
    const selected = new Set(Array.isArray(rowIds) ? rowIds.filter(Boolean) : []);
    const fileTreeRowById = context.getFileTreeRowById();
    const itemById = context.getItemById();
    return Array.from(selected).filter((rowId) => {
      const item = fileTreeRowById.get(rowId);
      if (!item) return false;
      let cursor = item;
      while (cursor && cursor.parentNodeId) {
        const parent = itemById.get(cursor.parentNodeId) || null;
        if (parent && selected.has(parent.rowId)) {
          return false;
        }
        cursor = parent;
      }
      return true;
    });
  };

  const validateFileTreeWritableTarget = (target) => {
    const targetItem = target?.rowId ? context.getFileTreeRowById().get(target.rowId) : null;
    if (
      targetItem &&
      targetItem.capabilities.some((capability) =>
        capability === "readonly" ||
        capability === "readOnly" ||
        capability === "permissionDenied"
      )
    ) {
      return { ok: false, reason: "readonly", targetItem };
    }
    return { ok: true, reason: "", targetItem };
  };

  const getFileTreeRowLabel = (item) =>
    item?.title || item?.rowId || item?.nodeId || "选中项";

  const getFileTreeTargetLabel = (target) => {
    if (!target || target.rowKind === "root" || !target.rowId) return "Explorer 根目录";
    const item = context.getFileTreeRowById().get(target.rowId);
    if (!item) return target.rowId;
    if (item.rowKind === "markdown" || item.rowKind === "document" || item.rowKind === "index") {
      return `${item.title} 的父目录`;
    }
    return item.title;
  };

  const showFileTreePreflight = (preflight) =>
    new Promise((resolve) => {
      if (activeFileTreePreflightElement) {
        activeFileTreePreflightElement.remove();
        activeFileTreePreflightElement = null;
      }

      const backdrop = document.createElement("div");
      backdrop.className = "tree-preflight-backdrop";
      backdrop.dataset.testid = "tree-preflight";
      backdrop.dataset.preflightKind = preflight.kind || "";
      const dialog = document.createElement("div");
      dialog.className = "tree-preflight-dialog";
      dialog.setAttribute("role", "dialog");
      dialog.setAttribute("aria-modal", "true");
      const body = document.createElement("div");
      body.className = "tree-preflight-body";
      const title = document.createElement("h2");
      title.className = "tree-preflight-title";
      title.textContent = preflight.title || "操作预检";
      body.appendChild(title);

      const list = document.createElement("ul");
      list.className = "tree-preflight-list";
      (preflight.lines || []).forEach((line) => {
        const item = document.createElement("li");
        item.textContent = line;
        list.appendChild(item);
      });
      body.appendChild(list);
      dialog.appendChild(body);

      const actions = document.createElement("div");
      actions.className = "tree-preflight-actions";
      const cancel = document.createElement("button");
      cancel.type = "button";
      cancel.dataset.role = "cancel";
      cancel.textContent = "取消";
      const confirm = document.createElement("button");
      confirm.type = "button";
      confirm.dataset.role = "confirm";
      confirm.textContent = preflight.confirmLabel || "继续";
      actions.appendChild(cancel);
      actions.appendChild(confirm);
      dialog.appendChild(actions);
      backdrop.appendChild(dialog);

      const finish = (accepted) => {
        backdrop.remove();
        activeFileTreePreflightElement = null;
        document.removeEventListener("keydown", onKeyDown, true);
        resolve(accepted);
      };
      const onKeyDown = (event) => {
        if (event.key === "Escape") {
          event.preventDefault();
          finish(false);
        }
      };
      cancel.addEventListener("click", () => finish(false));
      confirm.addEventListener("click", () => finish(true));
      document.addEventListener("keydown", onKeyDown, true);
      document.body.appendChild(backdrop);
      activeFileTreePreflightElement = backdrop;
      cancel.focus({ preventScroll: true });
    });

  const runFileTreePreflight = async (kind, dropContext = {}) => {
    const sourceRowIds = Array.isArray(dropContext.rowIds)
      ? dropContext.rowIds.filter(Boolean)
      : [];
    const sourceItems = sourceRowIds
      .map((rowId) => context.getFileTreeRowById().get(rowId))
      .filter(Boolean);
    const targetLabel = getFileTreeTargetLabel(dropContext.target);
    const sourceLabel =
      sourceItems.length === 0
        ? "外部文件"
        : sourceItems.map(getFileTreeRowLabel).join("、");
    const fileNames = Array.isArray(dropContext.files)
      ? dropContext.files.map((file) => file.name || "未命名文件").filter(Boolean)
      : [];

    if (dropContext.validation && dropContext.validation.ok === false) {
      if (dropContext.validation.reason === "readonly") {
        await showFileTreePreflight({
          kind: "readonly",
          title: "操作预检失败",
          confirmLabel: "知道了",
          lines: [
            "目标: readonly",
            "原因: 当前目标为只读或权限不足。",
            "结果: 不会发送 execute。",
          ],
        });
        return false;
      }
      context.setLastAction(`预检拒绝: ${dropContext.validation.reason}`, "error");
      return false;
    }

    const writableTarget = validateFileTreeWritableTarget(dropContext.target);
    if (!writableTarget.ok) {
      await showFileTreePreflight({
        kind: "readonly",
        title: "操作预检失败",
        confirmLabel: "知道了",
        lines: [
          `目标: ${getFileTreeRowLabel(writableTarget.targetItem)}`,
          "原因: 当前目标为只读或权限不足。",
          "结果: 不会发送 execute。",
        ],
      });
      return false;
    }

    const preflight = (() => {
      if (kind === "delete") {
        return {
          kind,
          title: "删除预检",
          confirmLabel: "删除",
          lines: [
            `目标: ${sourceLabel}`,
            `影响: ${Math.max(1, sourceItems.length)} 个文件树节点`,
            context.sourceKind === "local_folder"
              ? "删除后进入本地 .mnote/trash。"
              : "删除将进入云端回收站。",
          ],
        };
      }
      if (kind === "paste") {
        return {
          kind,
          title: dropContext.copy ? "粘贴复制预检" : "粘贴移动预检",
          confirmLabel: "粘贴",
          lines: [
            `来源: ${sourceLabel}`,
            `目标: ${targetLabel}`,
            "命名冲突策略: 使用递增命名，不静默覆盖。",
          ],
        };
      }
      if (kind === "dropFiles") {
        return {
          kind,
          title: "外部拖入预检",
          confirmLabel: context.sourceKind === "local_folder" ? "拖入" : "发送",
          lines: [
            `文件: ${fileNames.join("、") || "外部文件"}`,
            `目标: ${targetLabel}`,
            context.sourceKind === "local_folder"
              ? "目标可写时复制进本地文件夹；命名冲突使用递增命名。"
              : "legacy cloud 目标交给宿主上传到对象存储；命名冲突由上传 executor 处理。",
          ],
        };
      }
      return {
        kind: dropContext.copy ? "copy" : "move",
        title: dropContext.copy ? "复制预检" : "移动预检",
        confirmLabel: dropContext.copy ? "复制" : "移动",
        lines: [
          `来源: ${sourceLabel}`,
          `目标: ${targetLabel}`,
          dropContext.copy
            ? "命名冲突策略: 使用递增命名，不静默覆盖。"
            : "会检查自拖自身、后代目标和跨 workspace 来源。",
        ],
      };
    })();

    return await showFileTreePreflight(preflight);
  };

  const executeLocalFileTreeInternalDrop = async (target, rowIds, copy, trigger = "drop") => {
    const validation = validateFileTreeInternalDrop(target, rowIds, copy);
    if (!validation.ok) {
      context.setLastAction(`已拒绝非法拖拽: ${validation.reason}`, "error");
      return false;
    }
    const accepted = await runFileTreePreflight(trigger === "paste" ? "paste" : "move", {
      target,
      rowIds,
      copy,
      validation,
    });
    if (!accepted) return false;
    const parentId = resolveLocalFileTreeParentId(target);
    const sourceRowIds = filterRedundantFileTreeRowIds(rowIds);
    if (sourceRowIds.length === 0) return false;
    for (const rowId of sourceRowIds) {
      const sourceItem = context.getFileTreeRowById().get(rowId);
      const documentId = getFileTreeRowDocumentId(sourceItem) || sourceItem?.nodeId || "";
      if (!documentId) continue;
      await context.sendCommand({
        action: copy ? "copy" : "move",
        workspaceId: context.workspaceId,
        documentId,
        parentId,
        sortOrder: 0,
      });
    }
    context.setStatus(copy ? "复制成功" : "移动成功");
    context.setLastAction(copy ? "本地文件树复制完成" : "本地文件树移动完成");
    context.scheduleRefresh();
    return true;
  };

  const executeLocalFileTreeExternalDrop = async (target, files) => {
    const droppedFiles = Array.from(files || []);
    if (droppedFiles.length === 0) return false;
    const accepted = await runFileTreePreflight("dropFiles", {
      target,
      files: droppedFiles,
    });
    if (!accepted) return false;
    const parentId = resolveLocalFileTreeParentId(target);
    const payload = await Promise.all(
      droppedFiles.map(async (file) => ({
        name: file.name || "dropped-file",
        text: await file.text(),
      })),
    );
    await context.sendCommand({
      action: "dropFiles",
      workspaceId: context.workspaceId,
      parentId,
      content: payload,
    });
    context.setStatus("外部文件拖入成功");
    context.setLastAction(`已拖入 ${payload.length} 个本地文件`);
    context.scheduleRefresh();
    return true;
  };

  const clearFileTreeDropFeedback = () => {
    if (fileTreeHoverExpandTimer) {
      window.clearTimeout(fileTreeHoverExpandTimer);
      fileTreeHoverExpandTimer = 0;
    }
    if (activeFileTreeDropRowId) {
      const previousRow = context.appElement.querySelector(
        `.tree-row[data-shell-mode="filetree"][data-row-id="${activeFileTreeDropRowId}"]`,
      );
      if (previousRow instanceof HTMLElement) {
        previousRow.dataset.dropTarget = "false";
        delete previousRow.dataset.dropPosition;
      }
    }
    activeFileTreeDropRowId = null;
    activeFileTreeDropPosition = null;
    activeFileTreeRootDrop = false;
    context.appElement.querySelectorAll('.tree-root[data-drop-target="true"]').forEach((element) => {
      if (element instanceof HTMLElement) {
        element.dataset.dropTarget = "false";
      }
    });
  };

  const setFileTreeDropFeedback = (target) => {
    const nextRowId = target?.rowId || null;
    const nextDropPosition = target?.dropPosition || "inside";
    if (activeFileTreeDropRowId && activeFileTreeDropRowId !== nextRowId) {
      const previousRow = context.appElement.querySelector(
        `.tree-row[data-shell-mode="filetree"][data-row-id="${activeFileTreeDropRowId}"]`,
      );
      if (previousRow instanceof HTMLElement) {
        previousRow.dataset.dropTarget = "false";
        delete previousRow.dataset.dropPosition;
      }
    }
    if (nextRowId) {
      const nextRow = context.appElement.querySelector(
        `.tree-row[data-shell-mode="filetree"][data-row-id="${nextRowId}"]`,
      );
      if (nextRow instanceof HTMLElement) {
        nextRow.dataset.dropTarget = "true";
        nextRow.dataset.dropPosition = nextDropPosition;
      }
      activeFileTreeDropRowId = nextRowId;
      activeFileTreeDropPosition = nextDropPosition;
      activeFileTreeRootDrop = false;
      if (fileTreeHoverExpandTimer) {
        window.clearTimeout(fileTreeHoverExpandTimer);
        fileTreeHoverExpandTimer = 0;
      }
      const targetItem = context.getFileTreeRowById().get(nextRowId);
      if (
        nextDropPosition === "inside" &&
        targetItem &&
        context.canExpandFileTreeRow(targetItem) &&
        !context.expanded.has(targetItem.nodeId)
      ) {
        fileTreeHoverExpandTimer = window.setTimeout(() => {
          context.expanded.add(targetItem.nodeId);
          fileTreeHoverExpandTimer = 0;
          context.renderTree();
        }, 650);
      }
      context.appElement.querySelectorAll('.tree-root[data-drop-target="true"]').forEach((element) => {
        if (element instanceof HTMLElement) {
          element.dataset.dropTarget = "false";
        }
      });
      return;
    }
    activeFileTreeDropRowId = null;
    activeFileTreeDropPosition = null;
    const root = context.appElement.querySelector(".tree-root");
    if (root instanceof HTMLElement) {
      root.dataset.dropTarget = "true";
    }
    activeFileTreeRootDrop = true;
  };

  const updateFileTreeDropFeedback = (rowId) => {
    if (rowId) {
      setFileTreeDropFeedback({ rowId });
      return;
    }
    clearFileTreeDropFeedback();
  };

  const inferDefaultFileTreeDropDocumentId = () => {
    const candidateRowIds = [
      context.getFileTreeFocusedRowId(),
      context.getFileTreeAnchorRowId(),
      ...Array.from(context.getSelectedFileTreeRowIds()),
    ].filter(Boolean);

    const fileTreeRowById = context.getFileTreeRowById();
    for (const rowId of candidateRowIds) {
      if (!rowId) continue;
      const item = fileTreeRowById.get(rowId);
      const documentId = getFileTreeRowDocumentId(item);
      if (documentId) {
        return documentId;
      }
    }

    const firstDocRowId = context.getVisibleFileTreeRowIds().find((rowId) => rowId.startsWith("doc:"));
    const firstDocItem = firstDocRowId ? fileTreeRowById.get(firstDocRowId) : null;
    return getFileTreeRowDocumentId(firstDocItem) || null;
  };

  return {
    clearFileTreeDropFeedback,
    executeLocalFileTreeExternalDrop,
    executeLocalFileTreeInternalDrop,
    filterRedundantFileTreeRowIds,
    getActiveFileTreeDropPosition: () => activeFileTreeDropPosition,
    getActiveFileTreeDropRowId: () => activeFileTreeDropRowId,
    getActiveFileTreeRootDrop: () => activeFileTreeRootDrop,
    getFileTreeDropTargetFromEvent,
    getFileTreeDropTargetFromElement,
    inferDefaultFileTreeDropDocumentId,
    runFileTreePreflight,
    setFileTreeDropFeedback,
    updateFileTreeDropFeedback,
    validateFileTreeInternalDrop,
  };
}
