import {
  CheckOutlined,
  DeleteOutlined,
  EditOutlined,
  FileAddOutlined,
  FileOutlined,
  FolderAddOutlined,
  FolderOpenOutlined,
  FolderOutlined,
  MoreOutlined,
  PlusOutlined,
  ReloadOutlined
} from '@ant-design/icons'
import { json as jsonLanguage } from '@codemirror/lang-json'
import { yaml as yamlLanguage } from '@codemirror/lang-yaml'
import CodeMirror from '@uiw/react-codemirror'
import {
  Alert,
  Button,
  Dropdown,
  Empty,
  Form,
  Input,
  Menu,
  Modal,
  Result,
  Select,
  Skeleton,
  Space,
  Spin,
  Tag,
  Tooltip,
  Tree,
  Typography,
  message
} from 'antd'
import type { DataNode } from 'antd/lib/tree'
import type { AxiosResponse } from 'axios'
import {
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState
} from 'react'
import { useHistory } from 'react-router-dom'

import {
  agentServiceCreateProject,
  agentServiceCreateProjectEntry,
  agentServiceDeleteProject,
  agentServiceDeleteProjectEntry,
  agentServiceGetProjectFile,
  agentServiceListProjectEntries,
  agentServiceListProjects,
  agentServiceMoveProjectEntry,
  agentServiceUpdateProjectFile
} from 'apiClient/services/agent-gateway'
import type {
  V1Agent,
  V1Entry,
  V1EntryType,
  V1FileContent,
  V1Project,
  V1ProjectRef
} from 'apiClient/services/agent-gateway/interface'

import {
  WorkspaceDraft,
  WorkspaceDraftContext,
  WorkspaceDraftIdentity,
  moveWorkspaceDrafts,
  readWorkspaceDraft,
  removeWorkspaceDraft,
  removeWorkspaceDrafts,
  scheduleWorkspaceDraft,
  writeWorkspaceDraft
} from '../drafts'
import {
  PAGE_SIZE,
  WorkspaceType,
  baseName,
  formatFileSize,
  formatModifiedAt,
  getAgentErrorCode,
  getNonEditableReason,
  getSafeErrorMessage,
  isSameOrDescendant,
  joinPath,
  parentPath,
  projectQuery,
  projectRef,
  replacePathPrefix,
  sortEntries,
  sortProjects,
  validateEntryName,
  validateProjectName
} from '../utils'

const MIN_BROWSER_WIDTH = 240
const MAX_BROWSER_WIDTH = 640
const MIN_FILE_PANEL_WIDTH = 680
const BROWSER_RESIZER_WIDTH = 8

interface DirectoryState {
  entries: V1Entry[]
  error?: string
  loaded: boolean
  loading: boolean
  nextCursor?: string
}

interface AgentTreeNode extends DataNode {
  entry?: V1Entry
  path: string
  pseudo?: 'empty' | 'error' | 'load-more'
}

interface EntryModalState {
  entry?: V1Entry
  parentPath?: string
  type?: V1EntryType
}

const emptyDirectoryState: DirectoryState = {
  entries: [],
  loaded: false,
  loading: false
}

export interface ProjectsHandle {
  beforeLeave: (action: () => void) => void
}

interface ProjectsProps {
  agent: V1Agent
  browserWidth: number
  onBrowserWidthChange: (width: number) => void
}

type ProjectSelection =
  | { workspace: 'global' }
  | { workspace: 'projects'; projectName: string }

const GLOBAL_PROJECT: ProjectSelection = { workspace: 'global' }

export default forwardRef<ProjectsHandle, ProjectsProps>(function Projects(
  { agent: selectedAgent, browserWidth, onBrowserWidthChange: setBrowserWidth },
  ref
) {
  const history = useHistory()
  const selectedAgentId = selectedAgent.agentId!
  const [selection, setSelection] = useState<ProjectSelection>(GLOBAL_PROJECT)
  const workspace = selection.workspace
  const selectedProjectName =
    selection.workspace === 'projects' ? selection.projectName : undefined
  const [projects, setProjects] = useState<V1Project[]>([])
  const [projectsLoading, setProjectsLoading] = useState(false)
  const [projectsError, setProjectsError] = useState<string>()
  const [directories, setDirectories] = useState<
    Record<string, DirectoryState>
  >({})
  const [expandedPaths, setExpandedPaths] = useState<string[]>([])
  const [selectedEntry, setSelectedEntry] = useState<V1Entry>()
  const [openedFile, setOpenedFile] = useState<V1FileContent>()
  const [originalContent, setOriginalContent] = useState('')
  const [editorContent, setEditorContent] = useState('')
  const [cachedContent, setCachedContent] = useState('')
  const [localSaveFailed, setLocalSaveFailed] = useState(false)
  const [staleDraft, setStaleDraft] = useState<WorkspaceDraft>()
  const [fileLoading, setFileLoading] = useState(false)
  const [fileError, setFileError] = useState<string>()
  const [workspaceUnavailable, setWorkspaceUnavailable] = useState(false)
  const [conflictNotice, setConflictNotice] = useState(false)
  const [createProjectOpen, setCreateProjectOpen] = useState(false)
  const [deleteProjectOpen, setDeleteProjectOpen] = useState(false)
  const [entryModal, setEntryModal] = useState<EntryModalState>()
  const [renameEntry, setRenameEntry] = useState<V1Entry>()
  const [moveEntry, setMoveEntry] = useState<V1Entry>()
  const [deleteEntry, setDeleteEntry] = useState<V1Entry>()
  const [moveDestination, setMoveDestination] = useState<string>()
  const [submitting, setSubmitting] = useState(false)
  const [saving, setSaving] = useState(false)
  const [saveError, setSaveError] = useState<string>()
  const [saveConflictOpen, setSaveConflictOpen] = useState(false)
  const [projectForm] = Form.useForm<{ name: string }>()
  const [deleteProjectForm] = Form.useForm<{ confirmation: string }>()
  const [entryForm] = Form.useForm<{ name: string }>()
  const [renameForm] = Form.useForm<{ name: string }>()
  const projectNameValue = Form.useWatch('name', projectForm)
  const projectDeleteConfirmation = Form.useWatch(
    'confirmation',
    deleteProjectForm
  )
  const entryNameValue = Form.useWatch('name', entryForm)
  const renameNameValue = Form.useWatch('name', renameForm)
  const identityRef = useRef('')
  const selectionRef = useRef(selection)
  const selectionVersionRef = useRef(0)
  const projectsRequestRef = useRef(0)
  if (selectionRef.current !== selection) {
    selectionVersionRef.current += 1
    selectionRef.current = selection
  }
  const workspaceRef = useRef<HTMLDivElement>(null)
  const browserResizeRef = useRef<{
    maxWidth: number
    pointerId: number
    startWidth: number
    startX: number
  }>()
  const directoriesRef = useRef<Record<string, DirectoryState>>({})
  const fileRequestRef = useRef(0)
  const selectedEntryRef = useRef<V1Entry>()
  const dirtyRef = useRef(false)
  const cancelLocalSaveRef = useRef<() => void>()

  const workspaceIdentity = `${selectedAgentId || ''}:${workspace}:${
    selectedProjectName || ''
  }:${selectionVersionRef.current}`
  identityRef.current = workspaceIdentity
  directoriesRef.current = directories

  const dirty =
    !!openedFile?.entry?.editable && editorContent !== originalContent
  const pendingLocalChanges =
    !!openedFile?.entry?.editable && editorContent !== cachedContent
  selectedEntryRef.current = selectedEntry
  dirtyRef.current = dirty

  const clearEditor = useCallback(() => {
    cancelLocalSaveRef.current?.()
    cancelLocalSaveRef.current = undefined
    fileRequestRef.current += 1
    setOpenedFile(undefined)
    setOriginalContent('')
    setEditorContent('')
    setCachedContent('')
    setLocalSaveFailed(false)
    setStaleDraft(undefined)
    setFileError(undefined)
    setFileLoading(false)
    setSaveConflictOpen(false)
    setSaveError(undefined)
  }, [])

  const discardChanges = useCallback(() => {
    setEditorContent(cachedContent)
    setLocalSaveFailed(false)
    setSaveConflictOpen(false)
  }, [cachedContent])

  const draftContext = useMemo<WorkspaceDraftContext | undefined>(
    () =>
      selectedAgentId
        ? {
            agentId: selectedAgentId,
            projectName: selectedProjectName,
            workspace
          }
        : undefined,
    [selectedAgentId, selectedProjectName, workspace]
  )

  const draftIdentity = useMemo<WorkspaceDraftIdentity | undefined>(
    () =>
      draftContext && openedFile?.entry?.path
        ? { ...draftContext, path: openedFile.entry.path }
        : undefined,
    [draftContext, openedFile?.entry?.path]
  )

  const persistLocalDraft = useCallback(() => {
    cancelLocalSaveRef.current?.()
    cancelLocalSaveRef.current = undefined
    if (!draftIdentity || !openedFile?.entry?.editable) {
      return true
    }

    try {
      if (editorContent === originalContent) {
        removeWorkspaceDraft(draftIdentity)
      } else {
        writeWorkspaceDraft({
          content: editorContent,
          identity: draftIdentity,
          revision: openedFile.entry.revision
        })
      }
      setCachedContent(editorContent)
      setLocalSaveFailed(false)
      return true
    } catch {
      setLocalSaveFailed(true)
      return false
    }
  }, [draftIdentity, editorContent, openedFile?.entry, originalContent])

  const afterDirtyCheck = useCallback(
    (action: () => void) => {
      if (!pendingLocalChanges || persistLocalDraft()) {
        action()
        return
      }

      Modal.confirm({
        title: 'Leave without a cached copy?',
        content: `Your latest changes to "${baseName(
          openedFile?.entry?.path || ''
        )}" could not be cached.`,
        cancelText: 'Stay',
        okText: 'Discard latest changes',
        okButtonProps: { danger: true },
        autoFocusButton: 'cancel',
        onOk: () => {
          discardChanges()
          action()
        }
      })
    },
    [
      discardChanges,
      openedFile?.entry?.path,
      pendingLocalChanges,
      persistLocalDraft
    ]
  )

  useImperativeHandle(ref, () => ({ beforeLeave: afterDirtyCheck }), [
    afterDirtyCheck
  ])
  const beforeLeaveRef = useRef(afterDirtyCheck)
  beforeLeaveRef.current = afterDirtyCheck

  useEffect(
    () => () => {
      projectsRequestRef.current += 1
      fileRequestRef.current += 1
    },
    []
  )

  useEffect(() => {
    cancelLocalSaveRef.current?.()
    cancelLocalSaveRef.current = undefined
    if (!pendingLocalChanges) {
      return
    }

    setLocalSaveFailed(false)
    const cancel = scheduleWorkspaceDraft(() => {
      void persistLocalDraft()
    })
    cancelLocalSaveRef.current = cancel
    return () => {
      cancel()
      if (cancelLocalSaveRef.current === cancel) {
        cancelLocalSaveRef.current = undefined
      }
    }
  }, [pendingLocalChanges, persistLocalDraft])

  useEffect(() => {
    const handleBeforeUnload = (event: BeforeUnloadEvent) => {
      if (!pendingLocalChanges || persistLocalDraft()) {
        return
      }
      event.preventDefault()
      event.returnValue = ''
    }
    window.addEventListener('beforeunload', handleBeforeUnload)
    return () => window.removeEventListener('beforeunload', handleBeforeUnload)
  }, [pendingLocalChanges, persistLocalDraft])

  useEffect(() => {
    if (!pendingLocalChanges) {
      return
    }

    let replaying = false
    const unblock = history.block((location, action) => {
      if (replaying) {
        return
      }
      if (persistLocalDraft()) {
        return
      }
      Modal.confirm({
        title: 'Leave without a cached copy?',
        content: `Your latest changes to "${baseName(
          openedFile?.entry?.path || ''
        )}" could not be cached.`,
        cancelText: 'Stay',
        okText: 'Discard latest changes',
        okButtonProps: { danger: true },
        autoFocusButton: 'cancel',
        onOk: () => {
          discardChanges()
          replaying = true
          unblock()
          if (action === 'REPLACE') {
            history.replace(location)
          } else if (action === 'POP') {
            history.goBack()
          } else {
            history.push(location)
          }
        }
      })
      return false
    })

    return unblock
  }, [
    discardChanges,
    history,
    openedFile?.entry?.path,
    pendingLocalChanges,
    persistLocalDraft
  ])

  const resetTree = useCallback(() => {
    setDirectories({})
    setExpandedPaths([])
    setSelectedEntry(undefined)
    setConflictNotice(false)
    clearEditor()
  }, [clearEditor])

  const loadDirectory = useCallback(
    async (path: string, reset = false, identity = workspaceIdentity) => {
      if (!selectedAgentId) {
        return
      }
      const ref = projectRef(workspace, selectedProjectName)
      if (workspace === 'projects' && !ref.projectName) {
        return
      }

      const currentState = directoriesRef.current[path] || emptyDirectoryState
      const cursor = reset ? undefined : currentState.nextCursor
      setDirectories((current) => {
        const state = current[path] || emptyDirectoryState
        return {
          ...current,
          [path]: { ...state, error: undefined, loading: true }
        }
      })

      try {
        const response = await agentServiceListProjectEntries(
          selectedAgentId,
          {
            ...projectQuery(workspace, selectedProjectName),
            path,
            pageSize: PAGE_SIZE,
            cursor
          },
          { isShowMessage: false }
        )
        if (identityRef.current !== identity) {
          return
        }
        const incoming = response.data.entries || []
        setDirectories((current) => {
          const previous = current[path] || emptyDirectoryState
          const entries = reset
            ? incoming
            : [
                ...previous.entries,
                ...incoming.filter(
                  (entry) =>
                    !previous.entries.some(
                      (existing) => existing.path === entry.path
                    )
                )
              ]
          return {
            ...current,
            [path]: {
              entries: sortEntries(entries),
              loaded: true,
              loading: false,
              nextCursor: response.data.nextCursor
            }
          }
        })
        const currentSelection = selectedEntryRef.current
        if (
          reset &&
          !response.data.nextCursor &&
          currentSelection?.path &&
          parentPath(currentSelection.path) === path &&
          !incoming.some((entry) => entry.path === currentSelection.path) &&
          !dirtyRef.current
        ) {
          setSelectedEntry(undefined)
          clearEditor()
          message.info('The selected item was removed.')
        }
        if (!path) {
          setWorkspaceUnavailable(false)
        }
      } catch (error) {
        if (identityRef.current !== identity) {
          return
        }
        if (getAgentErrorCode(error) === 'aborted' && cursor) {
          message.info('This folder changed. Refreshing its contents.')
          await loadDirectory(path, true, identity)
          return
        }
        if (getAgentErrorCode(error) === 'unavailable') {
          setWorkspaceUnavailable(true)
        }
        setDirectories((current) => ({
          ...current,
          [path]: {
            ...(current[path] || emptyDirectoryState),
            error: getSafeErrorMessage(error),
            loaded: true,
            loading: false
          }
        }))
      }
    },
    [
      clearEditor,
      selectedAgentId,
      selectedProjectName,
      workspace,
      workspaceIdentity
    ]
  )

  const loadProjects = useCallback(async () => {
    const requestId = ++projectsRequestRef.current
    const requestedSelection = selectionRef.current
    setProjectsLoading(true)
    setProjectsError(undefined)
    try {
      const response = await agentServiceListProjects(selectedAgentId, {
        isShowMessage: false
      })
      if (projectsRequestRef.current !== requestId) {
        return
      }
      const availableProjects = sortProjects(response.data.projects || [])
      setProjects(availableProjects)
      if (
        selectionRef.current === requestedSelection &&
        requestedSelection.workspace === 'projects' &&
        !availableProjects.some(
          (project) => project.name === requestedSelection.projectName
        )
      ) {
        beforeLeaveRef.current(() =>
          setSelection((current) =>
            current === requestedSelection ? GLOBAL_PROJECT : current
          )
        )
      }
    } catch (error) {
      if (projectsRequestRef.current === requestId) {
        setProjectsError(getSafeErrorMessage(error))
      }
    } finally {
      if (projectsRequestRef.current === requestId) {
        setProjectsLoading(false)
      }
    }
  }, [selectedAgentId])

  useEffect(() => {
    if (selectedAgent.projectManagementEnabled) {
      void loadProjects()
    }
  }, [loadProjects, selectedAgent.projectManagementEnabled])

  useEffect(() => {
    identityRef.current = workspaceIdentity
    resetTree()
    setWorkspaceUnavailable(false)
    if (selectedAgent.projectManagementEnabled) {
      void loadDirectory('', true, workspaceIdentity)
    }
    return () => {
      identityRef.current = ''
    }
  }, [
    loadDirectory,
    resetTree,
    selectedAgent.projectManagementEnabled,
    workspaceIdentity
  ])

  const openFile = useCallback(
    async (entry: V1Entry) => {
      if (!selectedAgentId) {
        return
      }
      cancelLocalSaveRef.current?.()
      cancelLocalSaveRef.current = undefined
      const identity = workspaceIdentity
      const requestId = fileRequestRef.current + 1
      fileRequestRef.current = requestId
      setSelectedEntry(entry)
      setOpenedFile(undefined)
      setOriginalContent('')
      setEditorContent('')
      setCachedContent('')
      setLocalSaveFailed(false)
      setStaleDraft(undefined)
      setFileError(undefined)
      setSaveError(undefined)
      setSaveConflictOpen(false)
      setFileLoading(true)
      try {
        const response = await agentServiceGetProjectFile(
          selectedAgentId,
          {
            ...projectQuery(workspace, selectedProjectName),
            path: entry.path
          },
          { isShowMessage: false }
        )
        if (
          identityRef.current !== identity ||
          fileRequestRef.current !== requestId
        ) {
          return
        }
        const file = response.data.file
        const openedEntry = file?.entry || entry
        const content = file?.content || ''
        setOpenedFile(file)
        setSelectedEntry(openedEntry)
        setOriginalContent(content)
        const fileDraftIdentity: WorkspaceDraftIdentity = {
          agentId: selectedAgentId,
          path: openedEntry.path || '',
          projectName: selectedProjectName,
          workspace
        }
        try {
          const draft = readWorkspaceDraft(fileDraftIdentity)
          if (!draft || draft.content === content) {
            if (draft) {
              removeWorkspaceDraft(fileDraftIdentity)
            }
            setEditorContent(content)
            setCachedContent(content)
          } else if (draft.revision === openedEntry.revision) {
            setEditorContent(draft.content)
            setCachedContent(draft.content)
          } else {
            setEditorContent(content)
            setCachedContent(content)
            setStaleDraft(draft)
          }
        } catch {
          setEditorContent(content)
          setCachedContent(content)
          setLocalSaveFailed(true)
        }
      } catch (error) {
        if (
          identityRef.current === identity &&
          fileRequestRef.current === requestId
        ) {
          setFileError(getSafeErrorMessage(error))
        }
      } finally {
        if (
          identityRef.current === identity &&
          fileRequestRef.current === requestId
        ) {
          setFileLoading(false)
        }
      }
    },
    [selectedAgentId, selectedProjectName, workspace, workspaceIdentity]
  )

  const handleSelectEntry = (entry: V1Entry) => {
    afterDirtyCheck(() => {
      if (entry.type === 'ENTRY_TYPE_DIRECTORY') {
        setSelectedEntry(entry)
        clearEditor()
      } else {
        void openFile(entry)
      }
    })
  }

  const refreshTree = useCallback(async () => {
    const paths = Array.from(new Set(['', ...expandedPaths]))
    await Promise.all(
      paths.map((path) => loadDirectory(path, true, workspaceIdentity))
    )
  }, [expandedPaths, loadDirectory, workspaceIdentity])

  const handleRefresh = () => {
    afterDirtyCheck(() => {
      setConflictNotice(false)
      void loadProjects()
      void refreshTree()
    })
  }

  const buildTreeNodes = useCallback(
    (path: string): AgentTreeNode[] => {
      const state = directories[path]
      if (!state?.loaded) {
        return []
      }
      const nodes = state.entries.map((entry): AgentTreeNode => {
        const isDirectory = entry.type === 'ENTRY_TYPE_DIRECTORY'
        return {
          children:
            isDirectory && directories[entry.path || '']?.loaded
              ? buildTreeNodes(entry.path || '')
              : undefined,
          entry,
          icon: isDirectory ? <FolderOutlined /> : <FileOutlined />,
          isLeaf: !isDirectory,
          key: entry.path || '',
          path: entry.path || '',
          title: renderEntryTitle(entry)
        }
      })
      if (!state.entries.length && !state.loading) {
        nodes.push({
          disabled: true,
          isLeaf: true,
          key: `${path}:empty`,
          path,
          pseudo: 'empty',
          selectable: false,
          title: 'Empty folder'
        })
      }
      if (state.nextCursor) {
        nodes.push({
          isLeaf: true,
          key: `${path}:load-more`,
          path,
          pseudo: 'load-more',
          selectable: false,
          title: (
            <Button
              className="agents-tree-inline-action"
              loading={state.loading}
              size="small"
              type="link"
              onClick={(event) => {
                event.stopPropagation()
                void loadDirectory(path)
              }}
            >
              {state.loading ? 'Loading...' : 'Load more...'}
            </Button>
          )
        })
      }
      if (state.error) {
        nodes.push({
          isLeaf: true,
          key: `${path}:error`,
          path,
          pseudo: 'error',
          selectable: false,
          title: (
            <Space size={4}>
              <Typography.Text type="danger">Failed to load.</Typography.Text>
              <Button
                className="agents-tree-inline-action"
                size="small"
                type="link"
                onClick={(event) => {
                  event.stopPropagation()
                  void loadDirectory(path, true)
                }}
              >
                Retry
              </Button>
            </Space>
          )
        })
      }
      return nodes
    },
    // render helpers intentionally use the latest modal and selection state.
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [directories, loadDirectory]
  )

  function renderEntryTitle(entry: V1Entry) {
    return (
      <span className="agents-tree-node-title">
        <Tooltip title={entry.name} mouseEnterDelay={0.5}>
          <span className="agents-tree-node-name">{entry.name}</span>
        </Tooltip>
        <Dropdown
          overlay={renderEntryMenu(entry)}
          placement="bottomRight"
          trigger={['click']}
        >
          <Button
            aria-label={`More actions for ${entry.name}`}
            className="agents-tree-node-more"
            icon={<MoreOutlined />}
            size="small"
            type="text"
            onClick={(event) => event.stopPropagation()}
          />
        </Dropdown>
      </span>
    )
  }

  function renderEntryMenu(entry: V1Entry) {
    const isDirectory = entry.type === 'ENTRY_TYPE_DIRECTORY'
    const isSkillsRoot = workspace === 'global' && entry.path === 'skills'
    const isAgentsFile = workspace === 'global' && entry.path === 'AGENTS.md'

    return (
      <Menu>
        {isDirectory && (
          <>
            <Menu.Item
              key="new-file"
              icon={<FileAddOutlined />}
              onClick={() => openCreateEntry('ENTRY_TYPE_FILE', entry.path)}
            >
              New file
            </Menu.Item>
            <Menu.Item
              key="new-folder"
              icon={<FolderAddOutlined />}
              onClick={() =>
                openCreateEntry('ENTRY_TYPE_DIRECTORY', entry.path)
              }
            >
              New folder
            </Menu.Item>
          </>
        )}
        {!isSkillsRoot && !isAgentsFile && (
          <>
            <Menu.Item
              key="rename"
              icon={<EditOutlined />}
              onClick={() => guardEntryAction(entry, () => openRename(entry))}
            >
              Rename
            </Menu.Item>
            <Menu.Item
              key="move"
              icon={<FolderOpenOutlined />}
              onClick={() => guardEntryAction(entry, () => openMove(entry))}
            >
              Move
            </Menu.Item>
          </>
        )}
        {!isSkillsRoot && (
          <Menu.Item
            danger
            key="delete"
            icon={<DeleteOutlined />}
            onClick={() => guardEntryAction(entry, () => setDeleteEntry(entry))}
          >
            Delete
          </Menu.Item>
        )}
      </Menu>
    )
  }

  const guardEntryAction = (entry: V1Entry, action: () => void) => {
    const openedPath = openedFile?.entry?.path
    if (openedPath && isSameOrDescendant(openedPath, entry.path || '')) {
      afterDirtyCheck(action)
    } else {
      action()
    }
  }

  const openCreateEntry = (type: V1EntryType, path = '') => {
    entryForm.resetFields()
    if (workspace === 'global' && !path && type === 'ENTRY_TYPE_FILE') {
      entryForm.setFieldsValue({ name: 'AGENTS.md' })
    }
    setEntryModal({ parentPath: path, type })
  }

  const openRename = (entry: V1Entry) => {
    renameForm.setFieldsValue({ name: entry.name })
    setRenameEntry(entry)
  }

  const openMove = (entry: V1Entry) => {
    setMoveDestination(undefined)
    setMoveEntry(entry)
  }

  const handleCreateProject = async ({ name }: { name: string }) => {
    const validationError = validateProjectName(name)
    if (validationError) {
      projectForm.setFields([{ name: 'name', errors: [validationError] }])
      return
    }
    setSubmitting(true)
    try {
      const response = await agentServiceCreateProject(
        selectedAgentId!,
        { name },
        { isShowMessage: false }
      )
      const createdProject = response.data.project
      setCreateProjectOpen(false)
      projectForm.resetFields()
      if (createdProject) {
        setProjects((current) =>
          sortProjects([
            ...current.filter(
              (project) => project.name !== createdProject.name
            ),
            createdProject
          ])
        )
        setSelection({
          workspace: 'projects',
          projectName: createdProject.name!
        })
      }
      await loadProjects()
      message.success(`Project ${name} created`)
    } catch (error) {
      projectForm.setFields([
        {
          name: 'name',
          errors: [
            getAgentErrorCode(error) === 'already-exists'
              ? 'A Project with this name already exists.'
              : getSafeErrorMessage(error)
          ]
        }
      ])
    } finally {
      setSubmitting(false)
    }
  }

  const handleDeleteProject = async () => {
    setSubmitting(true)
    try {
      await agentServiceDeleteProject(
        selectedAgentId!,
        selectedProjectName!,
        { confirmName: selectedProjectName },
        { isShowMessage: false }
      )
      const deletedName = selectedProjectName!
      try {
        removeWorkspaceDrafts({
          agentId: selectedAgentId!,
          projectName: deletedName,
          workspace: 'projects'
        })
      } catch {
        message.warning('Project deleted, but its cached drafts remain.')
      }
      setDeleteProjectOpen(false)
      deleteProjectForm.resetFields()
      clearEditor()
      setSelection(GLOBAL_PROJECT)
      await loadProjects()
      message.success(`Project ${deletedName} deleted`)
    } catch (error) {
      deleteProjectForm.setFields([
        {
          name: 'confirmation',
          errors: [getSafeErrorMessage(error)]
        }
      ])
    } finally {
      setSubmitting(false)
    }
  }

  const handleCreateEntry = async ({ name }: { name: string }) => {
    const validationError = validateEntryName(name)
    if (validationError) {
      entryForm.setFields([{ name: 'name', errors: [validationError] }])
      return
    }
    setSubmitting(true)
    try {
      const response = await agentServiceCreateProjectEntry(
        selectedAgentId!,
        {
          name,
          parentPath: entryModal?.parentPath,
          project: projectRef(workspace, selectedProjectName),
          type: entryModal?.type
        },
        { isShowMessage: false }
      )
      const createdEntry = response.data.entry
      const createdType = entryModal?.type
      const targetParentPath = entryModal?.parentPath || ''
      setEntryModal(undefined)
      entryForm.resetFields()
      await loadDirectory(targetParentPath, true)
      if (createdEntry) {
        setSelectedEntry(createdEntry)
        if (createdType === 'ENTRY_TYPE_DIRECTORY') {
          setExpandedPaths((current) =>
            Array.from(new Set([...current, createdEntry.path || '']))
          )
          await loadDirectory(createdEntry.path || '', true)
        } else {
          await openFile(createdEntry)
        }
      }
      message.success(
        `${createdType === 'ENTRY_TYPE_DIRECTORY' ? 'Folder' : 'File'} created`
      )
    } catch (error) {
      entryForm.setFields([
        { name: 'name', errors: [getSafeErrorMessage(error)] }
      ])
    } finally {
      setSubmitting(false)
    }
  }

  const applyMovedEntry = async (
    source: V1Entry,
    destinationParentPath: string,
    destinationName: string,
    response: AxiosResponse<{ entry?: V1Entry }>
  ) => {
    const previousPath = source.path || ''
    const nextPath =
      response.data.entry?.path ||
      joinPath(destinationParentPath, destinationName)
    const previousParent = parentPath(previousPath)
    try {
      moveWorkspaceDrafts(
        {
          agentId: selectedAgentId!,
          projectName: selectedProjectName,
          workspace
        },
        previousPath,
        nextPath,
        response.data.entry?.revision
      )
    } catch {
      setLocalSaveFailed(true)
      message.warning('Item moved, but its cached drafts could not be updated.')
    }
    const updateSelectedEntry = (current?: V1Entry) => {
      if (!current?.path || !isSameOrDescendant(current.path, previousPath)) {
        return current
      }
      if (current.path === previousPath && response.data.entry) {
        return response.data.entry
      }
      return {
        ...current,
        name: current.path === previousPath ? destinationName : current.name,
        path: replacePathPrefix(current.path, previousPath, nextPath)
      }
    }
    const nextSelectedEntry = updateSelectedEntry(selectedEntryRef.current)
    selectedEntryRef.current = nextSelectedEntry
    setSelectedEntry(nextSelectedEntry)
    setOpenedFile((current) => {
      const currentPath = current?.entry?.path
      if (!current?.entry || !currentPath) {
        return current
      }
      if (!isSameOrDescendant(currentPath, previousPath)) {
        return current
      }
      return {
        ...current,
        entry: {
          ...current.entry,
          ...(currentPath === previousPath ? response.data.entry : {}),
          path: replacePathPrefix(currentPath, previousPath, nextPath)
        }
      }
    })
    setExpandedPaths((current) =>
      current.map((path) => replacePathPrefix(path, previousPath, nextPath))
    )
    await Promise.all(
      Array.from(new Set([previousParent, destinationParentPath])).map((path) =>
        loadDirectory(path, true)
      )
    )
  }

  const moveProjectEntry = async (
    entry: V1Entry,
    destinationParentPath: string,
    destinationName: string
  ) => {
    return agentServiceMoveProjectEntry(
      selectedAgentId!,
      {
        destinationName,
        destinationParentPath,
        expectedRevision: entry.revision,
        project: projectRef(workspace, selectedProjectName),
        sourcePath: entry.path
      },
      { isShowMessage: false }
    )
  }

  const handleRenameEntry = async ({ name }: { name: string }) => {
    const validationError = validateEntryName(name)
    if (validationError) {
      renameForm.setFields([{ name: 'name', errors: [validationError] }])
      return
    }
    setSubmitting(true)
    try {
      const source = renameEntry!
      const destinationParentPath = parentPath(source.path || '')
      const response = await moveProjectEntry(
        source,
        destinationParentPath,
        name
      )
      setRenameEntry(undefined)
      await applyMovedEntry(source, destinationParentPath, name, response)
      message.success('Item renamed')
    } catch (error) {
      if (getAgentErrorCode(error) === 'aborted') {
        setRenameEntry(undefined)
        setConflictNotice(true)
      } else {
        renameForm.setFields([
          { name: 'name', errors: [getSafeErrorMessage(error)] }
        ])
      }
    } finally {
      setSubmitting(false)
    }
  }

  const handleMoveEntry = async () => {
    if (moveDestination === undefined) {
      return
    }
    setSubmitting(true)
    try {
      const source = moveEntry!
      const response = await moveProjectEntry(
        source,
        moveDestination,
        source.name || ''
      )
      setMoveEntry(undefined)
      await applyMovedEntry(
        source,
        moveDestination,
        source.name || '',
        response
      )
      message.success('Item moved')
    } catch (error) {
      if (getAgentErrorCode(error) === 'aborted') {
        setMoveEntry(undefined)
        setConflictNotice(true)
      } else {
        message.error(getSafeErrorMessage(error))
      }
    } finally {
      setSubmitting(false)
    }
  }

  const handleDeleteEntry = async () => {
    const entry = deleteEntry!
    setSubmitting(true)
    try {
      await agentServiceDeleteProjectEntry(
        selectedAgentId!,
        {
          ...projectQuery(workspace, selectedProjectName),
          expectedRevision: entry.revision,
          path: entry.path,
          recursive: entry.type === 'ENTRY_TYPE_DIRECTORY'
        },
        { isShowMessage: false }
      )
      try {
        removeWorkspaceDrafts(
          {
            agentId: selectedAgentId!,
            projectName: selectedProjectName,
            workspace
          },
          entry.path
        )
      } catch {
        message.warning('Item deleted, but its cached drafts remain.')
      }
      setDeleteEntry(undefined)
      if (
        openedFile?.entry?.path &&
        isSameOrDescendant(openedFile.entry.path, entry.path || '')
      ) {
        clearEditor()
        selectedEntryRef.current = undefined
        setSelectedEntry(undefined)
      }
      await loadDirectory(parentPath(entry.path || ''), true)
      message.success('Item deleted')
    } catch (error) {
      if (getAgentErrorCode(error) === 'aborted') {
        setDeleteEntry(undefined)
        setConflictNotice(true)
      } else {
        message.error(getSafeErrorMessage(error))
      }
    } finally {
      setSubmitting(false)
    }
  }

  const handleSave = useCallback(async () => {
    if (!openedFile?.entry?.path || !selectedAgentId || !dirty || saving) {
      return
    }
    persistLocalDraft()
    setSaving(true)
    setSaveError(undefined)
    try {
      const response = await agentServiceUpdateProjectFile(
        selectedAgentId,
        {
          content: editorContent,
          expectedRevision: openedFile.entry.revision,
          path: openedFile.entry.path,
          project: projectRef(workspace, selectedProjectName)
        },
        { isShowMessage: false }
      )
      const updatedEntry = response.data.entry || openedFile.entry
      setOpenedFile({ content: editorContent, entry: updatedEntry })
      setSelectedEntry(updatedEntry)
      setOriginalContent(editorContent)
      setCachedContent(editorContent)
      setStaleDraft(undefined)
      if (draftIdentity) {
        try {
          removeWorkspaceDraft(draftIdentity)
          setLocalSaveFailed(false)
        } catch {
          setLocalSaveFailed(true)
          message.warning(
            'File saved, but its cached draft could not be cleared.'
          )
        }
      }
      await loadDirectory(parentPath(updatedEntry.path || ''), true)
      message.success('File saved')
    } catch (error) {
      if (getAgentErrorCode(error) === 'aborted') {
        setSaveConflictOpen(true)
      } else {
        setSaveError(getSafeErrorMessage(error))
      }
    } finally {
      setSaving(false)
    }
  }, [
    dirty,
    draftIdentity,
    editorContent,
    loadDirectory,
    openedFile,
    persistLocalDraft,
    selectedAgentId,
    selectedProjectName,
    saving,
    workspace
  ])

  useEffect(() => {
    const handleSaveShortcut = (event: KeyboardEvent) => {
      if (
        openedFile?.entry?.editable &&
        (event.ctrlKey || event.metaKey) &&
        event.key.toLowerCase() === 's'
      ) {
        event.preventDefault()
        if (dirty && persistLocalDraft()) {
          message.success('Changes cached')
        }
      }
    }
    window.addEventListener('keydown', handleSaveShortcut)
    return () => window.removeEventListener('keydown', handleSaveShortcut)
  }, [dirty, openedFile?.entry?.editable, persistLocalDraft])

  const handleCopyCurrentContent = async () => {
    try {
      await navigator.clipboard.writeText(editorContent)
      message.success('Content copied.')
    } catch (error) {
      message.error('Copy failed. Copy the content directly from the editor.')
    }
  }

  const handleReloadServerVersion = () => {
    const entry = openedFile?.entry
    try {
      if (draftIdentity) {
        removeWorkspaceDraft(draftIdentity)
      }
      setSaveConflictOpen(false)
      setLocalSaveFailed(false)
      if (entry) {
        void openFile(entry)
      }
    } catch {
      setLocalSaveFailed(true)
    }
  }

  const handleCopyStaleDraft = async () => {
    try {
      await navigator.clipboard.writeText(staleDraft!.content)
      message.success('Cached draft copied.')
    } catch {
      message.error('Copy failed. Try again from a supported browser.')
    }
  }

  const handleDiscardStaleDraft = () => {
    try {
      removeWorkspaceDraft(staleDraft!.identity)
      setStaleDraft(undefined)
      setLocalSaveFailed(false)
      message.success('Cached draft discarded.')
    } catch {
      setLocalSaveFailed(true)
    }
  }

  const handleRevert = () => {
    if (!dirty || saving) {
      return
    }

    Modal.confirm({
      autoFocusButton: 'cancel',
      cancelText: 'Keep changes',
      content: `"${baseName(
        openedFile?.entry?.path || ''
      )}" will be restored to the last server version. Its cached changes will be deleted.`,
      okButtonProps: { danger: true },
      okText: 'Revert',
      title: 'Revert local changes?',
      onOk: () => {
        cancelLocalSaveRef.current?.()
        cancelLocalSaveRef.current = undefined
        try {
          if (draftIdentity) {
            removeWorkspaceDraft(draftIdentity)
          }
        } catch {
          setLocalSaveFailed(true)
          message.error(
            'Cache could not be cleared. Changes were not reverted.'
          )
          return
        }
        setEditorContent(originalContent)
        setCachedContent(originalContent)
        setLocalSaveFailed(false)
        setSaveConflictOpen(false)
        setSaveError(undefined)
        message.success('Local changes reverted.')
      }
    })
  }

  const rootState = directories['']
  const globalAgentsFileExists = rootState?.entries.some(
    (entry) => entry.path === 'AGENTS.md'
  )
  const selectedDirectoryPath =
    selectedEntry?.type === 'ENTRY_TYPE_DIRECTORY' ? selectedEntry.path : ''
  const canCreateFile =
    workspace === 'projects'
      ? !!selectedProjectName
      : !!selectedDirectoryPath || !globalAgentsFileExists
  const canCreateDirectory =
    workspace === 'projects' ? !!selectedProjectName : !!selectedDirectoryPath

  const renderWorkspace = () => {
    if (!selectedAgent?.projectManagementEnabled) {
      return (
        <Result
          status="info"
          title="Agent is not managed"
          subTitle="Project management is not enabled for this Agent instance."
        />
      )
    }
    return (
      <>
        {workspaceUnavailable && (
          <Alert
            action={
              <Button size="small" onClick={handleRefresh}>
                Retry
              </Button>
            }
            banner
            message="Workspace files are unavailable"
            description="The Agent file service could not be reached. Try again later."
            type="error"
          />
        )}
        {conflictNotice && (
          <Alert
            action={
              <Button
                size="small"
                onClick={() => {
                  setConflictNotice(false)
                  void refreshTree()
                }}
              >
                Refresh
              </Button>
            }
            banner
            closable
            message="This item changed before the operation could be completed."
            type="warning"
            onClose={() => setConflictNotice(false)}
          />
        )}
        <div className="agents-workspace" ref={workspaceRef}>
          <aside className="agents-browser" style={{ width: browserWidth }}>
            <div className="agents-browser-heading">
              <Typography.Text strong>Project</Typography.Text>
              <Space align="center" className="agents-project-actions" size={4}>
                <Button
                  icon={<PlusOutlined />}
                  size="small"
                  type="primary"
                  onClick={() =>
                    afterDirtyCheck(() => {
                      projectForm.resetFields()
                      setCreateProjectOpen(true)
                    })
                  }
                >
                  Create
                </Button>
                <Dropdown
                  disabled={workspace === 'global'}
                  overlay={
                    <Menu>
                      <Menu.Item
                        danger
                        key="delete"
                        icon={<DeleteOutlined />}
                        onClick={() =>
                          afterDirtyCheck(() => {
                            deleteProjectForm.resetFields()
                            setDeleteProjectOpen(true)
                          })
                        }
                      >
                        Delete project
                      </Menu.Item>
                    </Menu>
                  }
                  trigger={['click']}
                >
                  <Button
                    aria-label="More project actions"
                    disabled={workspace === 'global'}
                    icon={<MoreOutlined />}
                    size="small"
                  />
                </Dropdown>
              </Space>
            </div>
            <Select
              aria-label="Project"
              className="agents-project-selector"
              loading={projectsLoading}
              value={
                workspace === 'global'
                  ? 'global'
                  : `project:${selectedProjectName}`
              }
              onChange={(value: string) =>
                afterDirtyCheck(() =>
                  setSelection(
                    value === 'global'
                      ? GLOBAL_PROJECT
                      : {
                          workspace: 'projects',
                          projectName: value.slice('project:'.length)
                        }
                  )
                )
              }
            >
              <Select.Option key="global" value="global">
                <span className="agents-project-option">
                  <span>Global</span>
                  <Tag color="blue">global</Tag>
                </span>
              </Select.Option>
              {projects.map((project) => (
                <Select.Option
                  key={`project:${project.name}`}
                  value={`project:${project.name}`}
                >
                  <span className="agents-project-option">
                    <span>{project.name}</span>
                    <Tag color={project.gitInitialized ? 'green' : 'default'}>
                      {project.gitInitialized ? 'initialized' : 'uninitialized'}
                    </Tag>
                  </span>
                </Select.Option>
              ))}
            </Select>
            {projectsError && (
              <Alert
                action={
                  <Button size="small" onClick={() => void loadProjects()}>
                    Retry
                  </Button>
                }
                message="Projects could not be loaded"
                description={projectsError}
                type="error"
              />
            )}
            <div className="agents-tree-toolbar">
              <Space size={4}>
                <Tooltip
                  title={
                    workspace === 'global' && !selectedDirectoryPath
                      ? 'Create AGENTS.md'
                      : 'New file'
                  }
                >
                  <Button
                    aria-label="New file"
                    disabled={!canCreateFile}
                    icon={<FileAddOutlined />}
                    size="small"
                    onClick={() =>
                      openCreateEntry('ENTRY_TYPE_FILE', selectedDirectoryPath)
                    }
                  />
                </Tooltip>
                <Tooltip title="New folder">
                  <Button
                    aria-label="New folder"
                    disabled={!canCreateDirectory}
                    icon={<FolderAddOutlined />}
                    size="small"
                    onClick={() =>
                      openCreateEntry(
                        'ENTRY_TYPE_DIRECTORY',
                        selectedDirectoryPath
                      )
                    }
                  />
                </Tooltip>
                <Tooltip title="Refresh">
                  <Button
                    aria-label="Refresh"
                    icon={<ReloadOutlined />}
                    loading={rootState?.loading}
                    size="small"
                    onClick={handleRefresh}
                  />
                </Tooltip>
              </Space>
            </div>
            {rootState?.loading && !rootState.loaded ? (
              <Spin className="agents-tree-loading" size="small" />
            ) : (
              <Tree
                blockNode
                showIcon
                treeData={buildTreeNodes('')}
                expandedKeys={expandedPaths}
                loadData={async (node) => {
                  const treeNode = node as AgentTreeNode
                  if (treeNode.entry?.type === 'ENTRY_TYPE_DIRECTORY') {
                    await loadDirectory(treeNode.path, true)
                  }
                }}
                selectedKeys={selectedEntry?.path ? [selectedEntry.path] : []}
                onExpand={(keys) => setExpandedPaths(keys as string[])}
                onSelect={(_, info) => {
                  const treeNode = info.node as AgentTreeNode
                  if (treeNode.entry) {
                    handleSelectEntry(treeNode.entry)
                  }
                }}
              />
            )}
          </aside>
          <div
            aria-label="Resize file browser"
            aria-orientation="vertical"
            aria-valuemax={MAX_BROWSER_WIDTH}
            aria-valuemin={MIN_BROWSER_WIDTH}
            aria-valuenow={browserWidth}
            className="agents-browser-resizer"
            role="separator"
            tabIndex={0}
            title="Drag to resize file browser"
            onKeyDown={(event) => {
              if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {
                return
              }

              event.preventDefault()
              const maxWidth = Math.max(
                MIN_BROWSER_WIDTH,
                Math.min(
                  MAX_BROWSER_WIDTH,
                  workspaceRef.current!.clientWidth -
                    MIN_FILE_PANEL_WIDTH -
                    BROWSER_RESIZER_WIDTH
                )
              )
              const direction = event.key === 'ArrowLeft' ? -1 : 1
              setBrowserWidth(
                Math.min(
                  Math.max(browserWidth + direction * 16, MIN_BROWSER_WIDTH),
                  maxWidth
                )
              )
            }}
            onPointerDown={(event) => {
              const maxWidth = Math.max(
                MIN_BROWSER_WIDTH,
                Math.min(
                  MAX_BROWSER_WIDTH,
                  workspaceRef.current!.clientWidth -
                    MIN_FILE_PANEL_WIDTH -
                    BROWSER_RESIZER_WIDTH
                )
              )
              browserResizeRef.current = {
                maxWidth,
                pointerId: event.pointerId,
                startWidth: browserWidth,
                startX: event.clientX
              }
              event.currentTarget.setPointerCapture(event.pointerId)
              event.preventDefault()
            }}
            onPointerMove={(event) => {
              const resize = browserResizeRef.current
              if (!resize || resize.pointerId !== event.pointerId) {
                return
              }

              setBrowserWidth(
                Math.min(
                  Math.max(
                    resize.startWidth + event.clientX - resize.startX,
                    MIN_BROWSER_WIDTH
                  ),
                  resize.maxWidth
                )
              )
            }}
            onPointerCancel={() => {
              browserResizeRef.current = undefined
            }}
            onPointerUp={(event) => {
              browserResizeRef.current = undefined
              event.currentTarget.releasePointerCapture(event.pointerId)
            }}
          />
          <main className="agents-file-panel">{renderFilePanel()}</main>
        </div>
      </>
    )
  }

  const renderFilePanel = () => {
    if (fileLoading) {
      return <Skeleton active />
    }
    if (fileError) {
      return (
        <Result
          status="error"
          title="File is unavailable"
          subTitle={fileError}
          extra={
            selectedEntry && (
              <Button
                type="primary"
                onClick={() => void openFile(selectedEntry)}
              >
                Retry
              </Button>
            )
          }
        />
      )
    }
    if (!openedFile?.entry) {
      return (
        <Empty
          description={
            workspace === 'projects' && !selectedProjectName
              ? 'No Project selected'
              : 'Select a file to view or edit.'
          }
          image={Empty.PRESENTED_IMAGE_SIMPLE}
        />
      )
    }
    const entry = openedFile.entry
    return (
      <div className="agents-editor">
        <div className="agents-file-header">
          <div className="agents-file-title-row">
            <div className="agents-file-title">
              <Tooltip title={entry.path}>
                <Typography.Text className="agents-file-path" strong>
                  {entry.name || baseName(entry.path || '')}
                </Typography.Text>
              </Tooltip>
              <Space className="agents-file-tags" size={4}>
                <Tag color={entry.editable ? 'blue' : undefined}>
                  {entry.editable ? 'Editable' : 'Not editable'}
                </Tag>
                {dirty && <Tag color="orange">Modified</Tag>}
              </Space>
            </div>
            {entry.editable && (
              <Space className="agents-file-actions" size={8}>
                <Button
                  disabled={!dirty}
                  loading={saving}
                  type="primary"
                  onClick={() => void handleSave()}
                >
                  Save
                </Button>
                <Button disabled={!dirty || saving} onClick={handleRevert}>
                  Revert
                </Button>
              </Space>
            )}
          </div>
          <div className="agents-file-meta-row">
            <Typography.Text type="secondary">
              {formatFileSize(entry.size)} ·{' '}
              {formatModifiedAt(entry.modifiedAt)}
            </Typography.Text>
            {localSaveFailed ? (
              <Typography.Text type="danger">Cache failed ●</Typography.Text>
            ) : pendingLocalChanges ? (
              <Typography.Text type="secondary">Caching…</Typography.Text>
            ) : dirty ? (
              <Typography.Text type="secondary">
                <CheckOutlined /> Cached locally
              </Typography.Text>
            ) : null}
          </div>
        </div>
        {entry.editable ? (
          <>
            {saveError && (
              <Alert
                closable
                message={saveError}
                type="error"
                onClose={() => setSaveError(undefined)}
              />
            )}
            <div className="agents-code-editor">
              <CodeMirror
                basicSetup={{
                  foldGutter: true,
                  highlightActiveLine: true,
                  lineNumbers: true
                }}
                editable={!saving}
                extensions={getEditorExtensions(entry.path || '')}
                height="100%"
                value={editorContent}
                onChange={setEditorContent}
              />
            </div>
          </>
        ) : (
          <Result
            status="warning"
            title="This file cannot be edited"
            subTitle={getNonEditableReason(entry.size)}
          />
        )}
      </div>
    )
  }

  return (
    <>
      {renderWorkspace()}

      <Modal
        cancelButtonProps={{ disabled: submitting }}
        closable={!submitting}
        confirmLoading={submitting}
        keyboard={!submitting}
        maskClosable={!submitting}
        okButtonProps={{ disabled: !!validateProjectName(projectNameValue) }}
        okText="Create"
        title="Create Project"
        visible={createProjectOpen}
        onCancel={() => setCreateProjectOpen(false)}
        onOk={() => projectForm.submit()}
      >
        <Form
          form={projectForm}
          layout="vertical"
          onFinish={handleCreateProject}
        >
          <Form.Item
            extra="Use letters, numbers, dots, underscores, and hyphens. The name must start with a letter or number and contain at most 128 characters."
            label="Project name"
            name="name"
            rules={[
              {
                validator: (_, value) =>
                  validateFormValue(value, validateProjectName)
              }
            ]}
          >
            <Input
              autoFocus
              disabled={submitting}
              onPressEnter={() => projectForm.submit()}
            />
          </Form.Item>
        </Form>
      </Modal>

      <Modal
        cancelButtonProps={{ disabled: submitting }}
        closable={!submitting}
        confirmLoading={submitting}
        keyboard={!submitting}
        maskClosable={!submitting}
        okButtonProps={{
          danger: true,
          disabled: projectDeleteConfirmation !== selectedProjectName
        }}
        okText="Delete permanently"
        title="Permanently delete Project?"
        visible={deleteProjectOpen}
        onCancel={() => setDeleteProjectOpen(false)}
        onOk={() => deleteProjectForm.submit()}
      >
        <Typography.Paragraph>
          This will permanently delete "{selectedProjectName}", including all
          files and Git metadata. This action cannot be undone.
        </Typography.Paragraph>
        <Form
          form={deleteProjectForm}
          layout="vertical"
          onFinish={handleDeleteProject}
          onValuesChange={() =>
            deleteProjectForm.validateFields(['confirmation'])
          }
        >
          <Form.Item
            label={`Type ${selectedProjectName} to confirm`}
            name="confirmation"
            rules={[
              {
                validator: (_, value) =>
                  value === selectedProjectName
                    ? Promise.resolve()
                    : Promise.reject(
                        new Error('Enter the complete Project name.')
                      )
              }
            ]}
          >
            <Input autoFocus disabled={submitting} />
          </Form.Item>
        </Form>
      </Modal>

      <Modal
        cancelButtonProps={{ disabled: submitting }}
        closable={!submitting}
        confirmLoading={submitting}
        keyboard={!submitting}
        maskClosable={!submitting}
        okButtonProps={{ disabled: !!validateEntryName(entryNameValue) }}
        okText="Create"
        title={
          entryModal?.type === 'ENTRY_TYPE_DIRECTORY'
            ? 'New folder'
            : 'New file'
        }
        visible={!!entryModal}
        onCancel={() => setEntryModal(undefined)}
        onOk={() => entryForm.submit()}
      >
        <Typography.Paragraph type="secondary">
          Location:{' '}
          {getDisplayLocation(
            workspace,
            selectedProjectName,
            entryModal?.parentPath
          )}
        </Typography.Paragraph>
        <Form form={entryForm} layout="vertical" onFinish={handleCreateEntry}>
          <Form.Item
            label={
              entryModal?.type === 'ENTRY_TYPE_DIRECTORY'
                ? 'Folder name'
                : 'File name'
            }
            name="name"
            rules={[
              {
                validator: (_, value) =>
                  validateFormValue(value, validateEntryName)
              }
            ]}
          >
            <Input
              autoFocus
              disabled={
                submitting ||
                (workspace === 'global' &&
                  !entryModal?.parentPath &&
                  entryModal?.type === 'ENTRY_TYPE_FILE')
              }
              onPressEnter={() => entryForm.submit()}
            />
          </Form.Item>
        </Form>
      </Modal>

      <Modal
        cancelButtonProps={{ disabled: submitting }}
        closable={!submitting}
        confirmLoading={submitting}
        keyboard={!submitting}
        maskClosable={!submitting}
        okButtonProps={{
          disabled:
            !!validateEntryName(renameNameValue) ||
            renameNameValue === renameEntry?.name
        }}
        okText="Rename"
        title="Rename item"
        visible={!!renameEntry}
        onCancel={() => setRenameEntry(undefined)}
        onOk={() => renameForm.submit()}
      >
        <Typography.Paragraph type="secondary">
          Location: {parentPath(renameEntry?.path || '') || 'Workspace root'}
        </Typography.Paragraph>
        <Form form={renameForm} layout="vertical" onFinish={handleRenameEntry}>
          <Form.Item
            label="New name"
            name="name"
            rules={[
              {
                validator: (_, value) =>
                  validateFormValue(value, validateEntryName)
              }
            ]}
          >
            <Input
              autoFocus
              disabled={submitting}
              onFocus={(event) => event.target.select()}
            />
          </Form.Item>
        </Form>
      </Modal>

      <Modal
        cancelButtonProps={{ disabled: submitting }}
        closable={!submitting}
        confirmLoading={submitting}
        keyboard={!submitting}
        maskClosable={!submitting}
        okButtonProps={{ disabled: moveDestination === undefined }}
        okText="Move"
        title={`Move "${moveEntry?.name || ''}"`}
        visible={!!moveEntry}
        onCancel={() => setMoveEntry(undefined)}
        onOk={() => void handleMoveEntry()}
      >
        {moveEntry && (
          <DirectoryPicker
            agentId={selectedAgentId!}
            project={projectRef(workspace, selectedProjectName)}
            rootLabel={
              workspace === 'projects' ? selectedProjectName! : 'Global'
            }
            source={moveEntry}
            workspace={workspace}
            onChange={setMoveDestination}
          />
        )}
        <Typography.Paragraph type="secondary">
          Current location:{' '}
          {parentPath(moveEntry?.path || '') || 'Workspace root'}
        </Typography.Paragraph>
      </Modal>

      <Modal
        cancelButtonProps={{ disabled: submitting }}
        closable={!submitting}
        confirmLoading={submitting}
        keyboard={!submitting}
        maskClosable={!submitting}
        okButtonProps={{ danger: true }}
        okText="Delete permanently"
        title={
          deleteEntry?.type === 'ENTRY_TYPE_DIRECTORY'
            ? 'Permanently delete folder and its contents?'
            : 'Permanently delete file?'
        }
        visible={!!deleteEntry}
        onCancel={() => setDeleteEntry(undefined)}
        onOk={() => void handleDeleteEntry()}
      >
        <Typography.Paragraph>
          "{deleteEntry?.path}"
          {deleteEntry?.type === 'ENTRY_TYPE_DIRECTORY'
            ? ' and everything inside it will be permanently deleted.'
            : ' will be permanently deleted.'}{' '}
          This action cannot be undone.
        </Typography.Paragraph>
      </Modal>

      <Modal
        footer={
          <Space>
            <Button onClick={() => void handleCopyStaleDraft()}>
              Copy cached draft
            </Button>
            <Button danger type="primary" onClick={handleDiscardStaleDraft}>
              Discard cached draft
            </Button>
          </Space>
        }
        title="Cached draft is out of date"
        visible={!!staleDraft}
        onCancel={() => setStaleDraft(undefined)}
      >
        <Typography.Paragraph>
          The server version of "{baseName(staleDraft?.identity.path || '')}"
          changed after this draft was cached. The cached draft was not loaded.
          Copy it before discarding it if you still need those changes.
        </Typography.Paragraph>
      </Modal>

      <Modal
        footer={
          <Space>
            <Button onClick={() => void handleCopyCurrentContent()}>
              Copy current content
            </Button>
            <Button type="primary" onClick={handleReloadServerVersion}>
              Reload server version
            </Button>
          </Space>
        }
        title="File changed on the server"
        visible={saveConflictOpen}
        onCancel={() => setSaveConflictOpen(false)}
      >
        <Typography.Paragraph>
          "{baseName(openedFile?.entry?.path || '')}" was modified after you
          opened it. Your local changes have not been submitted to the server.
          Reload the server version or copy your current content first.
        </Typography.Paragraph>
      </Modal>
    </>
  )
})

function getEditorExtensions(path: string) {
  const extension = path.toLowerCase().split('.').pop()
  if (extension === 'json') {
    return [jsonLanguage()]
  }
  if (extension === 'yaml' || extension === 'yml') {
    return [yamlLanguage()]
  }
  return []
}

function validateFormValue(
  value: string,
  validate: (value: string) => string | undefined
) {
  const error = validate(value)
  return error ? Promise.reject(new Error(error)) : Promise.resolve()
}

function getDisplayLocation(
  workspace: WorkspaceType,
  projectName?: string,
  path?: string
) {
  return [workspace === 'projects' ? projectName : 'Global', path]
    .filter(Boolean)
    .join('/')
}

interface DirectoryPickerProps {
  agentId: string
  onChange: (path: string) => void
  project: V1ProjectRef
  rootLabel: string
  source: V1Entry
  workspace: WorkspaceType
}

function DirectoryPicker({
  agentId,
  onChange,
  project,
  rootLabel,
  source,
  workspace
}: DirectoryPickerProps) {
  const [states, setStates] = useState<Record<string, DirectoryState>>({})
  const [expanded, setExpanded] = useState<string[]>(['__root__'])
  const statesRef = useRef<Record<string, DirectoryState>>({})
  statesRef.current = states

  const load = useCallback(
    async (path: string, reset = false) => {
      const currentState = statesRef.current[path] || emptyDirectoryState
      const cursor = reset ? undefined : currentState.nextCursor
      setStates((current) => ({
        ...current,
        [path]: {
          ...(current[path] || emptyDirectoryState),
          error: undefined,
          loading: true
        }
      }))
      try {
        const response = await agentServiceListProjectEntries(
          agentId,
          {
            ...(project.scope === 'PROJECT_SCOPE_GLOBAL'
              ? { 'project.scope': 'PROJECT_SCOPE_GLOBAL' as const }
              : {
                  'project.scope': 'PROJECT_SCOPE_PROJECT' as const,
                  'project.projectName': project.projectName
                }),
            pageSize: PAGE_SIZE,
            path,
            cursor
          },
          { isShowMessage: false }
        )
        setStates((current) => ({
          ...current,
          [path]: {
            entries: sortEntries(
              reset
                ? response.data.entries || []
                : [
                    ...(current[path]?.entries || []),
                    ...(response.data.entries || []).filter(
                      (entry) =>
                        !(current[path]?.entries || []).some(
                          (existing) => existing.path === entry.path
                        )
                    )
                  ]
            ),
            loaded: true,
            loading: false,
            nextCursor: response.data.nextCursor
          }
        }))
      } catch (error) {
        setStates((current) => ({
          ...current,
          [path]: {
            ...(current[path] || emptyDirectoryState),
            error: getSafeErrorMessage(error),
            loaded: true,
            loading: false
          }
        }))
      }
    },
    [agentId, project.projectName, project.scope]
  )

  useEffect(() => {
    void load('', true)
  }, [load])

  const buildNodes = useCallback(
    (path: string): DataNode[] => {
      const directories = (states[path]?.entries || []).filter(
        (entry) => entry.type === 'ENTRY_TYPE_DIRECTORY'
      )
      const nodes: DataNode[] = directories.map((entry) => ({
        children: states[entry.path || '']?.loaded
          ? buildNodes(entry.path || '')
          : undefined,
        disabled:
          (source.type === 'ENTRY_TYPE_DIRECTORY' &&
            isSameOrDescendant(entry.path || '', source.path || '')) ||
          (workspace === 'global' && entry.path !== 'skills' && path === ''),
        icon: <FolderOutlined />,
        isLeaf: false,
        key: entry.path || '',
        title: entry.name
      }))
      const state = states[path]
      if (state?.nextCursor) {
        nodes.push({
          isLeaf: true,
          key: `__load_more__:${path}`,
          selectable: false,
          title: (
            <Button
              className="agents-tree-inline-action"
              loading={state.loading}
              size="small"
              type="link"
              onClick={(event) => {
                event.stopPropagation()
                void load(path)
              }}
            >
              Load more...
            </Button>
          )
        })
      }
      if (state?.error) {
        nodes.push({
          isLeaf: true,
          key: `__error__:${path}`,
          selectable: false,
          title: (
            <Button
              className="agents-tree-inline-action"
              danger
              size="small"
              type="link"
              onClick={(event) => {
                event.stopPropagation()
                void load(path, true)
              }}
            >
              Failed to load. Retry
            </Button>
          )
        })
      }
      return nodes
    },
    [load, source.path, source.type, states, workspace]
  )

  const rootChildren = buildNodes('')
  const treeData: DataNode[] = [
    {
      children: rootChildren,
      disabled: workspace === 'global',
      icon: <FolderOpenOutlined />,
      key: '__root__',
      title: rootLabel
    }
  ]

  return (
    <div className="agents-directory-picker">
      <Typography.Text strong>Move to</Typography.Text>
      <Tree
        blockNode
        showIcon
        expandedKeys={expanded}
        treeData={treeData}
        loadData={async (node) => {
          if (node.key !== '__root__' && !String(node.key).startsWith('__')) {
            await load(String(node.key), true)
          }
        }}
        onExpand={(keys) => setExpanded(keys as string[])}
        onSelect={(keys) => {
          if (keys.length) {
            onChange(keys[0] === '__root__' ? '' : String(keys[0]))
          }
        }}
      />
    </div>
  )
}
