import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import {
  XIcon,
  FileTextIcon,
  FileCodeIcon,
  FileIcon,
  FolderIcon,
  FolderOpenIcon,
  FolderPlusIcon,
  FilePlusIcon,
  Trash2Icon,
  ExternalLinkIcon,
  SaveIcon,
  Loader2Icon,
  ChevronRightIcon,
  PencilIcon,
} from "lucide-react";
import { UnsavedChangesDialog } from "../ui/UnsavedChangesDialog";
import { useToast } from "../ui/Toast";
import { scheduleAllSaveSync } from "../../services/webdav-save-sync";
import "./SkillFileEditor.css";

// ─── Types ──────────────────────────────────────────────

interface SkillFileEditorProps {
  skillId: string;
  localPath?: string;
  /** Human-readable skill name shown in the modal header. Falls back to a
   *  truncated skillId when omitted. */
  skillName?: string;
  isOpen: boolean;
  onClose?: () => void;
  onSave?: () => void;
  /** "modal" (default for backward compat) renders in a portal overlay;
   *  "inline" renders as a plain panel – no portal, no backdrop, no header. */
  mode?: "modal" | "inline";
  onUnsavedChange?: (hasUnsaved: boolean) => void;
}

interface FileEntry {
  path: string;
  content: string;
  isDirectory: boolean;
}

interface FileTreeEntry {
  path: string;
  isDirectory: boolean;
  size?: number;
}

interface TreeNode {
  name: string;
  path: string;
  isDirectory: boolean;
  children: TreeNode[];
  depth: number;
}

interface ContextMenuState {
  x: number;
  y: number;
  path: string | null;
  isDirectory: boolean;
}

// ─── Helpers ────────────────────────────────────────────

function getFileIcon(name: string, isDirectory: boolean, isOpen: boolean) {
  if (isDirectory) {
    return isOpen ? (
      <FolderOpenIcon className="skill-file-editor__tree-item-icon" />
    ) : (
      <FolderIcon className="skill-file-editor__tree-item-icon" />
    );
  }
  const ext = name.split(".").pop()?.toLowerCase() || "";
  if (["md", "mdx"].includes(ext)) {
    return <FileTextIcon className="skill-file-editor__tree-item-icon" />;
  }
  if (
    [
      "py",
      "js",
      "ts",
      "tsx",
      "jsx",
      "sh",
      "yaml",
      "yml",
      "toml",
      "json",
      "css",
      "html",
    ].includes(ext)
  ) {
    return <FileCodeIcon className="skill-file-editor__tree-item-icon" />;
  }
  return <FileIcon className="skill-file-editor__tree-item-icon" />;
}

function isMarkdownFile(path: string): boolean {
  const ext = path.split(".").pop()?.toLowerCase() || "";
  return ["md", "mdx"].includes(ext);
}

function isHiddenSkillRepoEntry(path: string): boolean {
  return path
    .split("/")
    .some((segment) => segment === ".git" || segment === ".prompthub");
}

function buildTree(files: FileTreeEntry[]): TreeNode[] {
  const root: TreeNode[] = [];

  // Sort: directories first, then alphabetical
  const sorted = [...files].sort((a, b) => {
    if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
    return a.path.localeCompare(b.path);
  });

  for (const file of sorted) {
    const parts = file.path.split("/");
    let currentLevel = root;

    for (let i = 0; i < parts.length; i++) {
      const partName = parts[i];
      const partPath = parts.slice(0, i + 1).join("/");
      const isLast = i === parts.length - 1;

      let existing = currentLevel.find((n) => n.name === partName);

      if (!existing) {
        existing = {
          name: partName,
          path: partPath,
          isDirectory: isLast ? file.isDirectory : true,
          children: [],
          depth: i,
        };
        currentLevel.push(existing);
      }

      if (!isLast) {
        currentLevel = existing.children;
      }
    }
  }

  return root;
}

function formatFileSize(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

// ─── Sub-components ─────────────────────────────────────

function SimpleDialog({
  isOpen,
  title,
  children,
  onClose,
}: {
  isOpen: boolean;
  title: string;
  children: React.ReactNode;
  onClose: () => void;
}) {
  if (!isOpen) return null;
  return createPortal(
    <div className="skill-file-editor__dialog-overlay">
      <div className="skill-file-editor__dialog-backdrop" onClick={onClose} />
      <div className="skill-file-editor__dialog">
        <h3>{title}</h3>
        {children}
      </div>
    </div>,
    document.body,
  );
}

// ─── Main Component ─────────────────────────────────────

export function SkillFileEditor({
  skillId,
  localPath,
  skillName,
  isOpen,
  onClose,
  onSave,
  mode = "modal",
  onUnsavedChange,
}: SkillFileEditorProps) {
  const { t } = useTranslation();
  const { showToast } = useToast();
  const isInline = mode === "inline";

  // State
  const [files, setFiles] = useState<FileTreeEntry[]>([]);
  const [loadedFiles, setLoadedFiles] = useState<Record<string, FileEntry>>({});
  const [isLoading, setIsLoading] = useState(false);
  const [loadingFilePath, setLoadingFilePath] = useState<string | null>(null);
  const [selectedFile, setSelectedFile] = useState<string | null>(null);
  const [modifiedFiles, setModifiedFiles] = useState<Record<string, string>>(
    {},
  );
  const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set());

  // Dialog states
  const [newFileDialogOpen, setNewFileDialogOpen] = useState(false);
  const [newFolderDialogOpen, setNewFolderDialogOpen] = useState(false);
  const [deleteDialogFile, setDeleteDialogFile] = useState<string | null>(null);
  const [renameDialogPath, setRenameDialogPath] = useState<string | null>(null);
  const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
  const [createParentPath, setCreateParentPath] = useState<string | null>(null);
  const [dialogInput, setDialogInput] = useState("");
  const [isSaving, setIsSaving] = useState(false);
  const [isUnsavedDialogOpen, setIsUnsavedDialogOpen] = useState(false);
  const [pendingUnsavedAction, setPendingUnsavedAction] = useState<
    (() => void) | null
  >(null);

  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const activeSourceKeyRef = useRef<string | null>(null);
  const isPathMode = Boolean(localPath);
  const sourceKey = localPath ? `path:${localPath}` : `skill:${skillId}`;

  const listFiles = useCallback(async () => {
    if (localPath) {
      return window.api.skill.listLocalFilesByPath(localPath);
    }
    return window.api.skill.listLocalFiles(skillId);
  }, [localPath, skillId]);

  const readFile = useCallback(
    async (relativePath: string) => {
      if (localPath) {
        return window.api.skill.readLocalFileByPath(localPath, relativePath);
      }
      return window.api.skill.readLocalFile(skillId, relativePath);
    },
    [localPath, skillId],
  );

  const writeFile = useCallback(
    async (relativePath: string, content: string) => {
      if (localPath) {
        return window.api.skill.writeLocalFileByPath(localPath, relativePath, content);
      }
      return window.api.skill.writeLocalFile(skillId, relativePath, content);
    },
    [localPath, skillId],
  );

  const createDir = useCallback(
    async (relativePath: string) => {
      if (localPath) {
        return window.api.skill.createLocalDirByPath(localPath, relativePath);
      }
      return window.api.skill.createLocalDir(skillId, relativePath);
    },
    [localPath, skillId],
  );

  const renamePath = useCallback(
    async (oldRelativePath: string, newRelativePath: string) => {
      if (localPath) {
        return window.api.skill.renameLocalPathByPath(
          localPath,
          oldRelativePath,
          newRelativePath,
        );
      }
      return window.api.skill.renameLocalPath(
        skillId,
        oldRelativePath,
        newRelativePath,
      );
    },
    [localPath, skillId],
  );

  const deleteFile = useCallback(
    async (relativePath: string) => {
      if (localPath) {
        return window.api.skill.deleteLocalFileByPath(localPath, relativePath);
      }
      return window.api.skill.deleteLocalFile(skillId, relativePath);
    },
    [localPath, skillId],
  );

  // Load files
  const loadFiles = useCallback(async () => {
    setIsLoading(true);
    try {
      const result = await listFiles();
      const visibleEntries = result.filter(
        (entry) => !isHiddenSkillRepoEntry(entry.path),
      );
      setFiles(visibleEntries);
      const firstFile =
        visibleEntries.find(
          (entry) =>
            !entry.isDirectory && entry.path.toLowerCase() === "skill.md",
        )?.path ||
        visibleEntries.find((entry) => !entry.isDirectory)?.path ||
        null;
      setSelectedFile((current) => {
        if (
          current &&
          visibleEntries.some((entry) => entry.path === current)
        ) {
          return current;
        }
        return firstFile;
      });
      setLoadedFiles((prev) => {
        const next: Record<string, FileEntry> = {};
        for (const entry of visibleEntries) {
          if (!entry.isDirectory && prev[entry.path]) {
            next[entry.path] = prev[entry.path];
          }
        }
        return next;
      });
      // Auto-expand all directories
      const dirs = visibleEntries
        .filter((entry) => entry.isDirectory)
        .map((entry) => entry.path);
      setExpandedDirs(new Set(dirs));
    } catch (error) {
      console.error("Failed to load skill files:", error);
      showToast(
        `${t("skill.loadFailed", "Load failed")}: ${String(error)}`,
        "error",
      );
    } finally {
      setIsLoading(false);
    }
  }, [listFiles, showToast, t]);

  const hasAnyUnsaved = useMemo(
    () => Object.keys(modifiedFiles).length > 0,
    [modifiedFiles],
  );

  useEffect(() => {
    if (!isOpen) {
      activeSourceKeyRef.current = null;
      return;
    }

    // Re-run file bootstrap only when the editor opens or switches to a
    // different skill/path source. Callback identity changes (for example from
    // i18n updates) must not wipe in-progress edits.
    if (activeSourceKeyRef.current === sourceKey) {
      return;
    }

    activeSourceKeyRef.current = sourceKey;
    void loadFiles();
    setModifiedFiles({});
  }, [isOpen, loadFiles, sourceKey]);

  useEffect(() => {
    onUnsavedChange?.(hasAnyUnsaved);
    (
      window as Window & { __PROMPTHUB_SKILL_EDITOR_DIRTY?: boolean }
    ).__PROMPTHUB_SKILL_EDITOR_DIRTY = hasAnyUnsaved;

    return () => {
      (
        window as Window & { __PROMPTHUB_SKILL_EDITOR_DIRTY?: boolean }
      ).__PROMPTHUB_SKILL_EDITOR_DIRTY = false;
    };
  }, [hasAnyUnsaved, onUnsavedChange]);

  useEffect(() => {
    if (!contextMenu) {
      return;
    }

    const closeContextMenu = () => setContextMenu(null);
    window.addEventListener("click", closeContextMenu);
    window.addEventListener("blur", closeContextMenu);
    return () => {
      window.removeEventListener("click", closeContextMenu);
      window.removeEventListener("blur", closeContextMenu);
    };
  }, [contextMenu]);

  useEffect(() => {
    if (!isOpen || !hasAnyUnsaved) {
      return;
    }

    const handleBeforeUnload = (event: BeforeUnloadEvent) => {
      event.preventDefault();
      event.returnValue = "";
    };

    window.addEventListener("beforeunload", handleBeforeUnload);
    return () => {
      window.removeEventListener("beforeunload", handleBeforeUnload);
    };
  }, [hasAnyUnsaved, isOpen]);

  const discardUnsavedChanges = useCallback(() => {
    setModifiedFiles({});
  }, []);

  const runWithUnsavedChangesCheck = useCallback((action: () => void) => {
    if (!hasAnyUnsaved) {
      action();
      return;
    }

    setPendingUnsavedAction(() => action);
    setIsUnsavedDialogOpen(true);
  }, [hasAnyUnsaved]);

  const loadSelectedFileContent = useCallback(
    async (path: string) => {
      if (path in modifiedFiles || loadedFiles[path]) {
        return;
      }
      setLoadingFilePath(path);
      try {
        const result = await readFile(path);
        if (result && !result.isDirectory) {
          setLoadedFiles((prev) => ({ ...prev, [path]: result }));
        }
      } catch (error) {
        console.error("Failed to read skill file:", error);
        showToast(
          `${t("skill.loadFailed", "Load failed")}: ${String(error)}`,
          "error",
        );
      } finally {
        setLoadingFilePath((current) => (current === path ? null : current));
      }
    },
    [loadedFiles, modifiedFiles, readFile, showToast, t],
  );

  useEffect(() => {
    if (!selectedFile) {
      return;
    }
    const currentMeta = files.find(
      (file) => file.path === selectedFile && !file.isDirectory,
    );
    if (!currentMeta) {
      return;
    }
    void loadSelectedFileContent(selectedFile);
  }, [files, loadSelectedFileContent, selectedFile]);

  // Build tree
  const tree = useMemo(() => buildTree(files), [files]);

  // Current file data
  const currentFile = useMemo(() => {
    if (!selectedFile) return null;
    const fileMeta =
      files.find((f) => f.path === selectedFile && !f.isDirectory) || null;
    if (!fileMeta) return null;
    return (
      loadedFiles[selectedFile] || {
        path: fileMeta.path,
        content: "",
        isDirectory: false,
      }
    );
  }, [files, loadedFiles, selectedFile]);

  const currentContent = useMemo(() => {
    if (!selectedFile) return "";
    if (selectedFile in modifiedFiles) return modifiedFiles[selectedFile];
    return currentFile?.content || "";
  }, [selectedFile, modifiedFiles, currentFile]);

  const isModified = useCallback(
    (path: string) => path in modifiedFiles,
    [modifiedFiles],
  );

  // Edit content
  const handleContentChange = useCallback(
    (newContent: string) => {
      if (!selectedFile) return;
      const original = currentFile?.content || "";
      if (newContent === original) {
        setModifiedFiles((prev) => {
          const next = { ...prev };
          delete next[selectedFile];
          return next;
        });
      } else {
        setModifiedFiles((prev) => ({ ...prev, [selectedFile]: newContent }));
      }
    },
    [selectedFile, currentFile],
  );

  // Save current file
  const saveCurrentFile = useCallback(async () => {
    if (!selectedFile || !(selectedFile in modifiedFiles)) return;
    setIsSaving(true);
    try {
      const nextContent = modifiedFiles[selectedFile];
      await writeFile(selectedFile, nextContent);
      setFiles((prev) =>
        prev.map((file) =>
          file.path === selectedFile
            ? {
                ...file,
                size: new TextEncoder().encode(nextContent).length,
              }
            : file,
        ),
      );
      setLoadedFiles((prev) => ({
        ...prev,
        [selectedFile]: {
          path: selectedFile,
          content: nextContent,
          isDirectory: false,
        },
      }));
      setModifiedFiles((prev) => {
        const next = { ...prev };
        delete next[selectedFile];
        return next;
      });
      if (!isPathMode) {
        scheduleAllSaveSync("skill:file-save");
      }
      showToast(t("skill.fileSaved", "File saved"), "success");
      if (onSave) {
        await onSave();
      }
    } catch (error) {
      console.error("Failed to save file:", error);
      showToast(
        `${t("skill.updateFailed", "Update failed")}: ${String(error)}`,
        "error",
      );
    } finally {
      setIsSaving(false);
    }
  }, [
    modifiedFiles,
    onSave,
    selectedFile,
    showToast,
    t,
    writeFile,
  ]);

  // Keyboard shortcuts
  useEffect(() => {
    if (!isOpen) return;
    const handleKeyDown = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && (e.key === "s" || e.key === "S")) {
        e.preventDefault();
        saveCurrentFile();
      }
      // Escape only closes in modal mode
      if (e.key === "Escape" && !isInline && onClose) {
        // Don't close if a dialog is open
        if (newFileDialogOpen || newFolderDialogOpen || deleteDialogFile)
          return;
        runWithUnsavedChangesCheck(() => {
          onClose();
        });
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [
    isOpen,
    isInline,
    saveCurrentFile,
    onClose,
    newFileDialogOpen,
    newFolderDialogOpen,
    deleteDialogFile,
    runWithUnsavedChangesCheck,
  ]);

  // Toggle directory
  const toggleDir = useCallback((path: string) => {
    setExpandedDirs((prev) => {
      const next = new Set(prev);
      if (next.has(path)) {
        next.delete(path);
      } else {
        next.add(path);
      }
      return next;
    });
  }, []);

  // New file
  const handleNewFile = useCallback(async () => {
    const rawName = dialogInput.trim();
    const name = createParentPath
      ? [createParentPath, rawName].filter(Boolean).join("/")
      : rawName;
    if (!name) return;
    try {
      // If path has intermediate dirs, create them first
      const dirParts = name.split("/");
      if (dirParts.length > 1) {
        const dirPath = dirParts.slice(0, -1).join("/");
        await createDir(dirPath);
      }
      await writeFile(name, "");
      await loadFiles();
      setSelectedFile(name);
      setNewFileDialogOpen(false);
      setDialogInput("");
      setLoadedFiles((prev) => ({
        ...prev,
        [name]: { path: name, content: "", isDirectory: false },
      }));
      if (!isPathMode) {
        scheduleAllSaveSync("skill:file-create");
      }
    } catch (error) {
      console.error("Failed to create file:", error);
      showToast(`Failed to create file: ${String(error)}`, "error");
    }
  }, [createDir, createParentPath, dialogInput, loadFiles, showToast, writeFile]);

  // New folder
  const handleNewFolder = useCallback(async () => {
    const rawName = dialogInput.trim();
    const name = createParentPath
      ? [createParentPath, rawName].filter(Boolean).join("/")
      : rawName;
    if (!name) return;
    try {
      await createDir(name);
      await loadFiles();
      setExpandedDirs((prev) => new Set([...prev, name]));
      setNewFolderDialogOpen(false);
      setDialogInput("");
      if (!isPathMode) {
        scheduleAllSaveSync("skill:dir-create");
      }
    } catch (error) {
      console.error("Failed to create folder:", error);
      showToast(`Failed to create folder: ${String(error)}`, "error");
    }
  }, [createDir, createParentPath, dialogInput, loadFiles, showToast]);

  const handleRenamePath = useCallback(async () => {
    if (!renameDialogPath) return;
    const nextName = dialogInput.trim();
    if (!nextName) return;

    const pathParts = renameDialogPath.split("/");
    pathParts[pathParts.length - 1] = nextName;
    const nextPath = pathParts.join("/");

    try {
      await renamePath(renameDialogPath, nextPath);
      setModifiedFiles((prev) => {
        if (!(renameDialogPath in prev)) {
          return prev;
        }
        const next = { ...prev, [nextPath]: prev[renameDialogPath] };
        delete next[renameDialogPath];
        return next;
      });
      setLoadedFiles((prev) => {
        if (!prev[renameDialogPath]) {
          return prev;
        }
        const next = {
          ...prev,
          [nextPath]: { ...prev[renameDialogPath], path: nextPath },
        };
        delete next[renameDialogPath];
        return next;
      });
      if (selectedFile === renameDialogPath) {
        setSelectedFile(nextPath);
      }
      await loadFiles();
      setRenameDialogPath(null);
      setDialogInput("");
      if (!isPathMode) {
        scheduleAllSaveSync("skill:path-rename");
      }
      showToast(t("skill.fileSaved", "File saved"), "success");
    } catch (error) {
      console.error("Failed to rename path:", error);
      showToast(`Failed to rename: ${String(error)}`, "error");
    }
  }, [
    dialogInput,
    loadFiles,
    renameDialogPath,
    selectedFile,
    showToast,
    t,
    renamePath,
  ]);

  // Delete file
  const handleDeleteFile = useCallback(async () => {
    if (!deleteDialogFile) return;
    try {
      await deleteFile(deleteDialogFile);
      if (selectedFile === deleteDialogFile) {
        setSelectedFile(null);
      }
      // Remove from modifiedFiles if present
      setModifiedFiles((prev) => {
        const next = { ...prev };
        delete next[deleteDialogFile];
        return next;
      });
      await loadFiles();
      setDeleteDialogFile(null);
      setLoadedFiles((prev) => {
        const next = { ...prev };
        delete next[deleteDialogFile];
        return next;
      });
      if (!isPathMode) {
        scheduleAllSaveSync("skill:file-delete");
      }
    } catch (error) {
      console.error("Failed to delete file:", error);
      showToast(`Failed to delete file: ${String(error)}`, "error");
    }
  }, [deleteFile, deleteDialogFile, selectedFile, loadFiles, showToast]);

  const requestSelectFile = useCallback(
    (path: string) => {
      if (path === selectedFile) {
        return;
      }

      runWithUnsavedChangesCheck(() => {
        setSelectedFile(path);
      });
    },
    [runWithUnsavedChangesCheck, selectedFile],
  );

  // Open in system file manager
  const handleOpenInExplorer = useCallback(async () => {
    try {
      const repoPath = localPath ?? (await window.api.skill.getRepoPath(skillId));
      if (!repoPath) {
        showToast(t("skill.noLocalRepo", "No local repository found"), "error");
        return;
      }
      window.electron?.openPath?.(repoPath);
    } catch (error) {
      console.error("Failed to open in file manager:", error);
    }
  }, [localPath, skillId, showToast, t]);

  // ─── Render ──────────────────────────────────────────

  if (!isOpen) return null;

  // Render tree node recursively
  const renderTreeNode = (node: TreeNode): React.ReactNode => {
    const isExpanded = expandedDirs.has(node.path);
    const isActive = selectedFile === node.path;
    const modified = !node.isDirectory && isModified(node.path);
    const depthClass =
      node.depth <= 4
        ? `skill-file-editor__tree-item--depth-${node.depth}`
        : "";

    if (node.isDirectory) {
      return (
        <div key={node.path}>
          <button
            className={`skill-file-editor__tree-item skill-file-editor__tree-item--directory ${depthClass}`}
            onClick={() => toggleDir(node.path)}
            onContextMenu={(event) => {
              event.preventDefault();
              event.stopPropagation();
              setContextMenu({
                x: event.clientX,
                y: event.clientY,
                path: node.path,
                isDirectory: true,
              });
            }}
          >
            <ChevronRightIcon
              className="skill-file-editor__tree-item-icon"
              style={{
                transform: isExpanded ? "rotate(90deg)" : "none",
                transition: "transform 0.15s",
              }}
            />
            {getFileIcon(node.name, true, isExpanded)}
            <span className="skill-file-editor__tree-item-name">
              {node.name}
            </span>
          </button>
          {isExpanded && node.children.map((child) => renderTreeNode(child))}
        </div>
      );
    }

    return (
      <button
        key={node.path}
        className={`skill-file-editor__tree-item ${depthClass} ${
          isActive ? "skill-file-editor__tree-item--active" : ""
        }`}
        onClick={() => {
          requestSelectFile(node.path);
        }}
        onContextMenu={(event) => {
          event.preventDefault();
          event.stopPropagation();
          setContextMenu({
            x: event.clientX,
            y: event.clientY,
            path: node.path,
            isDirectory: false,
          });
        }}
      >
        {getFileIcon(node.name, false, false)}
        <span className="skill-file-editor__tree-item-name">{node.name}</span>
        {modified && <span className="skill-file-editor__tree-item-dot" />}
        <div
          role="button"
          tabIndex={0}
          className="skill-file-editor__tree-item-delete"
          onClick={(e) => {
            e.stopPropagation();
            setDeleteDialogFile(node.path);
          }}
          onKeyDown={(e) => {
            if (e.key === "Enter" || e.key === " ") {
              e.stopPropagation();
              setDeleteDialogFile(node.path);
            }
          }}
          title={t("skill.deleteFile", "Delete File")}
        >
          <Trash2Icon style={{ width: "0.75rem", height: "0.75rem" }} />
        </div>
      </button>
    );
  };

  // ─── Shared body (file tree + editor) ─────────────────
  const editorBody = (
    <>
      <div className="skill-file-editor__body">
        {/* Left: file tree */}
        <div className="skill-file-editor__tree">
          <div className="skill-file-editor__tree-header">
            <span className="skill-file-editor__tree-title">
              {t("skill.fileEditor", "Files")}
            </span>
            <div className="skill-file-editor__tree-actions">
              <button
                className="skill-file-editor__tree-btn"
                onClick={() => {
                  setDialogInput("");
                  setCreateParentPath(null);
                  setNewFileDialogOpen(true);
                }}
                title={t("skill.newFile", "New File")}
              >
                <FilePlusIcon
                  style={{ width: "0.875rem", height: "0.875rem" }}
                />
              </button>
              <button
                className="skill-file-editor__tree-btn"
                onClick={() => {
                  setDialogInput("");
                  setCreateParentPath(null);
                  setNewFolderDialogOpen(true);
                }}
                title={t("skill.newFolder", "New Folder")}
              >
                <FolderPlusIcon
                  style={{ width: "0.875rem", height: "0.875rem" }}
                />
              </button>
            </div>
          </div>

          <div
            className="skill-file-editor__tree-list"
            onContextMenu={(event) => {
              if (event.target !== event.currentTarget) {
                return;
              }
              event.preventDefault();
              setContextMenu({
                x: event.clientX,
                y: event.clientY,
                path: null,
                isDirectory: true,
              });
            }}
          >
            {isLoading ? (
              <div className="skill-file-editor__loading">
                <Loader2Icon style={{ width: "1rem", height: "1rem" }} />
              </div>
            ) : tree.length === 0 ? (
              <div className="skill-file-editor__tree-empty">
                <FileIcon
                  style={{ width: "1.5rem", height: "1.5rem", opacity: 0.4 }}
                />
                <span>
                  {t("skill.noFiles", "No local files for this skill")}
                </span>
              </div>
            ) : (
              tree.map((node) => renderTreeNode(node))
            )}
          </div>

          <div className="skill-file-editor__tree-footer">
            <button
              className="skill-file-editor__open-explorer-btn"
              onClick={handleOpenInExplorer}
            >
              <ExternalLinkIcon
                style={{ width: "0.75rem", height: "0.75rem" }}
              />
              {t("skill.openInExplorer", "Open in File Manager")}
            </button>
          </div>
        </div>

        {/* Right: editor */}
        <div className="skill-file-editor__editor">
          {!selectedFile || !currentFile ? (
            <div className="skill-file-editor__editor-empty">
              <FileTextIcon
                style={{ width: "2rem", height: "2rem", opacity: 0.3 }}
              />
              <span>
                {files.filter((f) => !f.isDirectory).length > 0
                  ? t("skill.noContent", "Select a file to edit")
                  : t("skill.noFiles", "No local files for this skill")}
              </span>
            </div>
          ) : (
            <>
              {/* Editor header */}
              <div className="skill-file-editor__editor-header">
                <div className="skill-file-editor__editor-file-name">
                  {getFileIcon(currentFile.path, false, false)}
                  {currentFile.path}
                  {isModified(selectedFile) && (
                    <span className="skill-file-editor__tree-item-dot" />
                  )}
                </div>
                <div className="skill-file-editor__editor-tabs">
                  <button
                    className="skill-file-editor__editor-tab skill-file-editor__editor-tab--active"
                    onClick={() => undefined}
                  >
                    {t("prompt.edit", "Edit")}
                  </button>
                  <button
                    className="skill-file-editor__editor-tab"
                    onClick={saveCurrentFile}
                    disabled={isSaving || !isModified(selectedFile)}
                    style={{
                      opacity: isSaving || !isModified(selectedFile) ? 0.4 : 1,
                    }}
                    title="Cmd/Ctrl+S"
                  >
                    {isSaving ? (
                      <Loader2Icon
                        style={{
                          width: "0.875rem",
                          height: "0.875rem",
                          animation: "spin 1s linear infinite",
                        }}
                      />
                    ) : (
                      <SaveIcon
                        style={{ width: "0.875rem", height: "0.875rem" }}
                      />
                    )}
                  </button>
                </div>
              </div>

              {/* Editor content */}
              <div className="skill-file-editor__editor-content">
                {loadingFilePath === selectedFile &&
                !(selectedFile in modifiedFiles) &&
                !loadedFiles[selectedFile] ? (
                  <div className="skill-file-editor__loading">
                    <Loader2Icon style={{ width: "1rem", height: "1rem" }} />
                  </div>
                ) : (
                  <textarea
                    ref={textareaRef}
                    className="skill-file-editor__textarea"
                    value={currentContent}
                    onChange={(e) => handleContentChange(e.target.value)}
                    spellCheck={false}
                  />
                )}
              </div>

              {/* Status bar */}
              <div className="skill-file-editor__status-bar">
                <div className="skill-file-editor__status-left">
                  <span className="skill-file-editor__status-path">
                    {selectedFile}
                  </span>
                </div>
                <div className="skill-file-editor__status-right">
                  <span>
                    {formatFileSize(
                      new TextEncoder().encode(currentContent).length,
                    )}
                  </span>
                  <span>UTF-8</span>
                  {isModified(selectedFile) && (
                    <span style={{ color: "hsl(var(--primary))" }}>
                      {t("skill.unsavedFile", "Unsaved")}
                    </span>
                  )}
                </div>
              </div>
            </>
          )}
        </div>
      </div>

      {/* New File Dialog */}
      <SimpleDialog
        isOpen={newFileDialogOpen}
        title={t("skill.newFile", "New File")}
        onClose={() => setNewFileDialogOpen(false)}
      >
        <input
          type="text"
          className="skill-file-editor__dialog-input"
          value={dialogInput}
          onChange={(e) => setDialogInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter") handleNewFile();
            if (e.key === "Escape") setNewFileDialogOpen(false);
          }}
          placeholder={t("skill.enterFileName", "Enter file name")}
          autoFocus
        />
        <p className="skill-file-editor__dialog-hint">
          e.g. helpers/utils.py, README.md
        </p>
        <div className="skill-file-editor__dialog-actions">
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--cancel"
            onClick={() => setNewFileDialogOpen(false)}
          >
            {t("common.cancel", "Cancel")}
          </button>
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--primary"
            onClick={handleNewFile}
            disabled={!dialogInput.trim()}
          >
            {t("common.confirm", "Confirm")}
          </button>
        </div>
      </SimpleDialog>

      {/* New Folder Dialog */}
      <SimpleDialog
        isOpen={newFolderDialogOpen}
        title={t("skill.newFolder", "New Folder")}
        onClose={() => setNewFolderDialogOpen(false)}
      >
        <input
          type="text"
          className="skill-file-editor__dialog-input"
          value={dialogInput}
          onChange={(e) => setDialogInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter") handleNewFolder();
            if (e.key === "Escape") setNewFolderDialogOpen(false);
          }}
          placeholder={t("skill.enterFolderName", "Enter folder name")}
          autoFocus
        />
        <div className="skill-file-editor__dialog-actions">
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--cancel"
            onClick={() => setNewFolderDialogOpen(false)}
          >
            {t("common.cancel", "Cancel")}
          </button>
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--primary"
            onClick={handleNewFolder}
            disabled={!dialogInput.trim()}
          >
            {t("common.confirm", "Confirm")}
          </button>
        </div>
      </SimpleDialog>

      {/* Delete Confirmation Dialog */}
      <SimpleDialog
        isOpen={!!deleteDialogFile}
        title={t("common.delete", "Delete")}
        onClose={() => setDeleteDialogFile(null)}
      >
        <p
          style={{
            fontSize: "0.85rem",
            color: "hsl(var(--muted-foreground))",
            marginBottom: "0.5rem",
          }}
        >
          {t(
            "skill.deletePathConfirm",
            "Are you sure you want to delete this file or folder? This action cannot be undone.",
          )}
        </p>
        <p
          style={{
            fontSize: "0.8rem",
            fontFamily: "monospace",
            background: "hsl(var(--muted) / 0.5)",
            padding: "0.375rem 0.5rem",
            borderRadius: "0.375rem",
          }}
        >
          {deleteDialogFile}
        </p>
        <div className="skill-file-editor__dialog-actions">
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--cancel"
            onClick={() => setDeleteDialogFile(null)}
          >
            {t("common.cancel", "Cancel")}
          </button>
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--destructive"
            onClick={handleDeleteFile}
          >
            {t("common.delete", "Delete")}
          </button>
        </div>
      </SimpleDialog>

      <SimpleDialog
        isOpen={!!renameDialogPath}
        title={t("folder.rename", "重命名")}
        onClose={() => setRenameDialogPath(null)}
      >
        <input
          type="text"
          className="skill-file-editor__dialog-input"
          value={dialogInput}
          onChange={(e) => setDialogInput(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === "Enter") handleRenamePath();
            if (e.key === "Escape") setRenameDialogPath(null);
          }}
          placeholder={t("skill.enterFileName", "Enter file name")}
          autoFocus
        />
        <div className="skill-file-editor__dialog-actions">
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--cancel"
            onClick={() => setRenameDialogPath(null)}
          >
            {t("common.cancel", "Cancel")}
          </button>
          <button
            className="skill-file-editor__dialog-btn skill-file-editor__dialog-btn--primary"
            onClick={handleRenamePath}
            disabled={!dialogInput.trim()}
          >
            {t("common.confirm", "Confirm")}
          </button>
        </div>
      </SimpleDialog>

      {contextMenu && (
        <div
          className="skill-file-editor__context-menu"
          style={{ left: contextMenu.x, top: contextMenu.y }}
        >
          {contextMenu.path && !contextMenu.isDirectory && (
            <>
              <button
                className="skill-file-editor__context-item"
                onClick={() => {
                  const currentName =
                    contextMenu.path?.split("/").pop() || contextMenu.path || "";
                  setDialogInput(currentName);
                  setRenameDialogPath(contextMenu.path);
                  setContextMenu(null);
                }}
              >
                <PencilIcon className="w-4 h-4" />
                {t("folder.rename", "重命名")}
              </button>
              <button
                className="skill-file-editor__context-item skill-file-editor__context-item--danger"
                onClick={() => {
                  setDeleteDialogFile(contextMenu.path);
                  setContextMenu(null);
                }}
              >
                <Trash2Icon className="w-4 h-4" />
                {t("common.delete", "Delete")}
              </button>
            </>
          )}
          <button
            className="skill-file-editor__context-item"
            onClick={() => {
              setDialogInput("");
              setCreateParentPath(
                contextMenu.path && contextMenu.isDirectory
                  ? contextMenu.path
                  : contextMenu.path?.split("/").slice(0, -1).join("/") || null,
              );
              setNewFileDialogOpen(true);
              setContextMenu(null);
            }}
          >
            <FilePlusIcon className="w-4 h-4" />
            {t("skill.newFile", "New File")}
          </button>
          <button
            className="skill-file-editor__context-item"
            onClick={() => {
              setDialogInput("");
              setCreateParentPath(
                contextMenu.path && contextMenu.isDirectory
                  ? contextMenu.path
                  : contextMenu.path?.split("/").slice(0, -1).join("/") || null,
              );
              setNewFolderDialogOpen(true);
              setContextMenu(null);
            }}
          >
            <FolderPlusIcon className="w-4 h-4" />
            {t("skill.newFolder", "New Folder")}
          </button>
          {contextMenu.path && contextMenu.isDirectory && (
            <>
              <button
                className="skill-file-editor__context-item"
                onClick={() => {
                  const currentName =
                    contextMenu.path?.split("/").pop() || contextMenu.path || "";
                  setDialogInput(currentName);
                  setRenameDialogPath(contextMenu.path);
                  setContextMenu(null);
                }}
              >
                <PencilIcon className="w-4 h-4" />
                {t("folder.rename", "重命名")}
              </button>
              <button
                className="skill-file-editor__context-item skill-file-editor__context-item--danger"
                onClick={() => {
                  setDeleteDialogFile(contextMenu.path);
                  setContextMenu(null);
                }}
              >
                <Trash2Icon className="w-4 h-4" />
                {t("common.delete", "Delete")}
              </button>
            </>
          )}
        </div>
      )}
    </>
  );

  // ─── Inline mode: render as a plain panel ─────────────
  if (isInline) {
    return (
      <>
        <div className="skill-file-editor skill-file-editor--inline">
          {editorBody}
        </div>
        <UnsavedChangesDialog
          isOpen={isUnsavedDialogOpen}
          onClose={() => {
            setIsUnsavedDialogOpen(false);
            setPendingUnsavedAction(null);
          }}
          onSave={() => {
            void saveCurrentFile().finally(() => {
              setIsUnsavedDialogOpen(false);
              pendingUnsavedAction?.();
              setPendingUnsavedAction(null);
            });
          }}
          onDiscard={() => {
            discardUnsavedChanges();
            setIsUnsavedDialogOpen(false);
            pendingUnsavedAction?.();
            setPendingUnsavedAction(null);
          }}
        />
      </>
    );
  }

  // ─── Modal mode: render in a portal with overlay ──────
  const modalContent = (
    <div className="fixed inset-0 z-[100] flex items-center justify-center">
      {/* Backdrop */}
      <div
        className="absolute inset-0 bg-black/50 backdrop-blur-sm"
        onClick={() => {
          runWithUnsavedChangesCheck(() => {
            onClose?.();
          });
        }}
      />

      {/* Modal */}
      <div className="relative app-wallpaper-panel-strong rounded-2xl shadow-2xl border border-border overflow-hidden animate-in fade-in zoom-in-95 duration-base skill-file-editor">
        {/* Header */}
        <div className="flex items-center justify-between px-6 py-3 border-b border-border shrink-0">
          <h2 className="text-base font-semibold flex items-center gap-2">
            {t("skill.fileEditor", "File Editor")}
            <span className="text-xs font-normal text-muted-foreground">
              —{" "}
              {skillName ||
                (isPathMode
                  ? localPath
                  : skillId.length > 16
                    ? `${skillId.slice(0, 8)}…${skillId.slice(-4)}`
                    : skillId)}
            </span>
          </h2>
          <div className="flex items-center gap-2">
            {hasAnyUnsaved && (
              <span className="text-xs text-amber-500 font-medium">
                {t("skill.unsavedFile", "File has unsaved changes")}
              </span>
            )}
            <button
              onClick={() => {
                runWithUnsavedChangesCheck(() => {
                  onClose?.();
                });
              }}
              className="p-2 text-muted-foreground hover:text-foreground hover:bg-accent rounded-lg transition-colors"
            >
              <XIcon className="w-4 h-4" />
            </button>
          </div>
        </div>

        {/* Body */}
        {editorBody}
      </div>
    </div>
  );

  return (
    <>
      {createPortal(modalContent, document.body)}
      <UnsavedChangesDialog
        isOpen={isUnsavedDialogOpen}
        onClose={() => {
          setIsUnsavedDialogOpen(false);
          setPendingUnsavedAction(null);
        }}
        onSave={() => {
          void saveCurrentFile().finally(() => {
            setIsUnsavedDialogOpen(false);
            pendingUnsavedAction?.();
            setPendingUnsavedAction(null);
          });
        }}
        onDiscard={() => {
          discardUnsavedChanges();
          setIsUnsavedDialogOpen(false);
          pendingUnsavedAction?.();
          setPendingUnsavedAction(null);
        }}
      />
    </>
  );
}
