import { useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useSearch } from '@tanstack/react-router'
import Editor from '@monaco-editor/react'
import {
  Archive,
  Copy,
  Download,
  FilePlus2,
  FolderInput,
  FolderPlus,
  Play,
  RefreshCw,
  Save,
  Trash2,
  Upload,
} from 'lucide-react'
import type { FileNode, MiseLanguage } from '@/api'
import { api } from '@/api'
import { useFileTree } from '@/api/hooks'
import { Button, Card, Input, Modal, PageHeader, Select } from '@/components/ui'
import { PATHS } from '@/constants'
import { useTheme } from '@/context/ThemeContext'
import { copyToClipboard } from '@/lib/format'
import { buildExecutionCommand } from '@/lib/repo-parser'
import { toast } from '@/lib/toast'
import { cn } from '@/lib/utils'

function flattenTree(nodes: FileNode[], depth = 0): Array<FileNode & { depth: number }> {
  const result: Array<FileNode & { depth: number }> = []
  for (const node of nodes || []) {
    result.push({ ...node, depth })
    if (node.children?.length) result.push(...flattenTree(node.children, depth + 1))
  }
  return result
}

function guessLanguage(path: string) {
  const ext = path.split('.').pop()?.toLowerCase() || ''
  const map: Record<string, string> = {
    js: 'javascript',
    jsx: 'javascript',
    ts: 'typescript',
    tsx: 'typescript',
    py: 'python',
    go: 'go',
    sh: 'shell',
    bash: 'shell',
    json: 'json',
    yml: 'yaml',
    yaml: 'yaml',
    md: 'markdown',
    html: 'html',
    css: 'css',
    php: 'php',
    rb: 'ruby',
    rs: 'rust',
  }
  return map[ext] || 'shell'
}

export default function ScriptEditor() {
  const { theme } = useTheme()
  const navigate = useNavigate()
  const search = useSearch({ strict: false }) as { file?: string }
  const { data: treeData, isLoading, refetch, isFetching } = useFileTree()
  const tree = Array.isArray(treeData) ? treeData : []
  const [selectedPath, setSelectedPath] = useState(search.file || '')
  const [content, setContent] = useState('')
  const [dirty, setDirty] = useState(false)
  const [loadingContent, setLoadingContent] = useState(false)
  const [newName, setNewName] = useState('')
  const [renameOpen, setRenameOpen] = useState(false)
  const [renameValue, setRenameValue] = useState('')
  const [message, setMessage] = useState('')
  const [filter, setFilter] = useState('')
  const [runOpen, setRunOpen] = useState(false)
  const [langs, setLangs] = useState<MiseLanguage[]>([])
  const [runPlugin, setRunPlugin] = useState('')
  const [runVersion, setRunVersion] = useState('')
  const [scriptsDir, setScriptsDir] = useState(PATHS.SCRIPTS_DIR)
  const [pathOpOpen, setPathOpOpen] = useState(false)
  const [pathOpMode, setPathOpMode] = useState<'move' | 'copy'>('move')
  const [pathOpTarget, setPathOpTarget] = useState('')
  const fileInputRef = useRef<HTMLInputElement>(null)
  const folderInputRef = useRef<HTMLInputElement>(null)
  const lastLoadedPath = useRef('')

  const flat = useMemo(() => flattenTree(tree), [tree])
  const filtered = useMemo(() => {
    if (!filter.trim()) return flat
    const q = filter.toLowerCase()
    return flat.filter((n) => n.path.toLowerCase().includes(q) || n.name.toLowerCase().includes(q))
  }, [flat, filter])

  const pluginVersions = useMemo(() => {
    const map: Record<string, string[]> = {}
    langs.forEach((l) => {
      if (!l.plugin) return
      if (!map[l.plugin]) map[l.plugin] = []
      if (l.version && !map[l.plugin]!.includes(l.version)) map[l.plugin]!.push(l.version)
    })
    return map
  }, [langs])

  useEffect(() => {
    if (search.file && search.file !== selectedPath) setSelectedPath(search.file)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [search.file])

  useEffect(() => {
    api.settings
      .getPaths()
      .then((p) => setScriptsDir(p?.scripts_dir || PATHS.SCRIPTS_DIR))
      .catch(() => setScriptsDir(PATHS.SCRIPTS_DIR))
    api.mise
      .list()
      .then((list) => setLangs(Array.isArray(list) ? list : []))
      .catch(() => setLangs([]))
  }, [])

  useEffect(() => {
    if (!selectedPath) return
    const node = flat.find((f) => f.path === selectedPath)
    if (node?.isDir) return
    if (lastLoadedPath.current === selectedPath && content) return
    let cancelled = false
    setLoadingContent(true)
    api.files
      .getContent(selectedPath)
      .then((res) => {
        if (cancelled) return
        setContent(res.content || '')
        setDirty(false)
        lastLoadedPath.current = selectedPath
      })
      .catch(() => {
        if (!cancelled) setContent('')
      })
      .finally(() => {
        if (!cancelled) setLoadingContent(false)
      })
    return () => {
      cancelled = true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedPath, flat])

  function selectPath(path: string) {
    if (dirty && selectedPath && selectedPath !== path) {
      if (!confirm('当前文件未保存，确认切换？')) return
    }
    setSelectedPath(path)
    navigate({ to: '/editor', search: { file: path || undefined } })
  }

  function openRunDialog() {
    if (!selectedPath || flat.find((f) => f.path === selectedPath)?.isDir) return
    const ext = selectedPath.split('.').pop()?.toLowerCase() || ''
    const extToLang: Record<string, string> = { py: 'python', js: 'node', mjs: 'node', ts: 'node', go: 'go' }
    const inferred = extToLang[ext] || Object.keys(pluginVersions)[0] || ''
    setRunPlugin(inferred)
    setRunVersion(pluginVersions[inferred]?.[0] || '')
    setRunOpen(true)
  }

  function confirmRun() {
    if (!selectedPath) return
    const envs = runPlugin && runVersion ? [{ plugin: runPlugin, version: runVersion }] : []
    const cmd = buildExecutionCommand(selectedPath, envs, scriptsDir)
    try {
      sessionStorage.setItem('taskpool_terminal_cmd', cmd)
    } catch {
      // ignore
    }
    setRunOpen(false)
    navigate({ to: '/terminal' })
  }

  async function save() {
    if (!selectedPath) return
    try {
      await api.files.saveContent(selectedPath, content)
      setDirty(false)
      setMessage('已保存')
      toast.success('已保存')
      refetch()
    } catch (e: any) {
      setMessage(e?.message || '保存失败')
      toast.error(e?.message || '保存失败')
    }
  }

  function currentDir() {
    const node = flat.find((f) => f.path === selectedPath)
    if (node?.isDir) return selectedPath
    if (selectedPath.includes('/')) return selectedPath.slice(0, selectedPath.lastIndexOf('/'))
    return ''
  }

  async function createFile(isDir: boolean) {
    if (!newName.trim()) return
    const base = currentDir()
    const path = base ? `${base}/${newName.trim()}` : newName.trim()
    try {
      await api.files.create(path, isDir)
      setNewName('')
      refetch()
      if (!isDir) selectPath(path)
      setMessage(isDir ? '目录已创建' : '文件已创建')
    } catch (e: any) {
      setMessage(e?.message || '创建失败')
    }
  }

  async function removeSelected() {
    if (!selectedPath) return
    if (!confirm(`确认删除 ${selectedPath}？`)) return
    try {
      await api.files.delete(selectedPath)
      setSelectedPath('')
      setContent('')
      setDirty(false)
      refetch()
      setMessage('已删除')
    } catch (e: any) {
      setMessage(e?.message || '删除失败')
    }
  }

  async function renameSelected() {
    if (!selectedPath || !renameValue.trim()) return
    const parent = selectedPath.includes('/') ? selectedPath.slice(0, selectedPath.lastIndexOf('/')) : ''
    const newPath = parent ? `${parent}/${renameValue.trim()}` : renameValue.trim()
    try {
      await api.files.rename(selectedPath, newPath)
      lastLoadedPath.current = ''
      selectPath(newPath)
      setRenameOpen(false)
      refetch()
      setMessage('已重命名')
      toast.success('已重命名')
    } catch (e: any) {
      setMessage(e?.message || '重命名失败')
      toast.error(e?.message || '重命名失败')
    }
  }

  function openPathOp(mode: 'move' | 'copy') {
    if (!selectedPath) return
    setPathOpMode(mode)
    setPathOpTarget(selectedPath)
    setPathOpOpen(true)
  }

  async function confirmPathOp() {
    if (!selectedPath || !pathOpTarget.trim()) return
    try {
      if (pathOpMode === 'move') {
        await api.files.move(selectedPath, pathOpTarget.trim())
        lastLoadedPath.current = ''
        selectPath(pathOpTarget.trim())
        setMessage('已移动')
        toast.success('已移动')
      } else {
        await api.files.copy(selectedPath, pathOpTarget.trim())
        setMessage('已复制')
        toast.success('已复制')
      }
      setPathOpOpen(false)
      refetch()
    } catch (e: any) {
      setMessage(e?.message || (pathOpMode === 'move' ? '移动失败' : '复制失败'))
      toast.error(e?.message || (pathOpMode === 'move' ? '移动失败' : '复制失败'))
    }
  }

  async function uploadFiles(files: FileList | null, asFolder = false) {
    if (!files?.length) return
    const target = currentDir()
    try {
      if (asFolder) {
        const paths: string[] = []
        for (let i = 0; i < files.length; i++) {
          const f = files[i]!
          paths.push((f as any).webkitRelativePath || f.name)
        }
        await api.files.uploadFiles(files, paths, target || undefined)
      } else if (files.length === 1 && /\.(zip|tar|gz|tgz)$/i.test(files[0]!.name)) {
        await api.files.uploadArchive(files[0]!, target || undefined)
      } else {
        const paths = Array.from(files).map((f) => f.name)
        await api.files.uploadFiles(files, paths, target || undefined)
      }
      refetch()
      setMessage('上传完成')
    } catch (e: any) {
      setMessage(e?.message || '上传失败')
    }
  }

  return (
    <div>
      <PageHeader
        title="脚本编辑"
        description="浏览脚本目录并在线编辑"
        actions={
          <>
            <Button variant="secondary" onClick={() => refetch()} disabled={isFetching}>
              <RefreshCw size={14} className={isFetching ? 'animate-spin' : ''} />
              刷新
            </Button>
            <Button onClick={save} disabled={!selectedPath || !dirty}>
              <Save size={14} />
              保存
            </Button>
          </>
        }
      />

      {message ? (
        <div className="mb-3 rounded-md border border-[var(--border)] bg-[var(--bg-tertiary)] px-3 py-2 text-sm">{message}</div>
      ) : null}

      <div className="grid gap-4 lg:grid-cols-[300px_1fr]">
        <Card className="overflow-hidden">
          <div className="space-y-2 border-b border-[var(--border)] p-3">
            <Input placeholder="过滤文件" value={filter} onChange={(e) => setFilter(e.target.value)} />
            <Input placeholder="新建名称" value={newName} onChange={(e) => setNewName(e.target.value)} />
            <div className="flex flex-wrap gap-2">
              <Button size="sm" variant="secondary" onClick={() => createFile(false)}>
                <FilePlus2 size={14} />
                文件
              </Button>
              <Button size="sm" variant="secondary" onClick={() => createFile(true)}>
                <FolderPlus size={14} />
                目录
              </Button>
              <Button
                size="sm"
                variant="secondary"
                disabled={!selectedPath}
                onClick={() => {
                  setRenameValue(selectedPath.split('/').pop() || '')
                  setRenameOpen(true)
                }}
              >
                重命名
              </Button>
              <Button size="sm" variant="secondary" disabled={!selectedPath} onClick={() => openPathOp('move')}>
                <FolderInput size={14} />
                移动
              </Button>
              <Button size="sm" variant="secondary" disabled={!selectedPath} onClick={() => openPathOp('copy')}>
                <Copy size={14} />
                复制
              </Button>
              <Button size="sm" variant="danger" onClick={removeSelected} disabled={!selectedPath}>
                <Trash2 size={14} />
              </Button>
            </div>
            <div className="flex flex-wrap gap-2">
              <input ref={fileInputRef} type="file" multiple className="hidden" onChange={(e) => uploadFiles(e.target.files)} />
              <input
                ref={folderInputRef}
                type="file"
                className="hidden"
                // @ts-expect-error webkitdirectory
                webkitdirectory=""
                multiple
                onChange={(e) => uploadFiles(e.target.files, true)}
              />
              <Button size="sm" variant="secondary" onClick={() => fileInputRef.current?.click()}>
                <Upload size={14} />
                上传
              </Button>
              <Button size="sm" variant="secondary" onClick={() => folderInputRef.current?.click()}>
                上传目录
              </Button>
              {selectedPath && !flat.find((f) => f.path === selectedPath)?.isDir ? (
                <a href={api.files.download(selectedPath)} className="inline-flex">
                  <Button size="sm" variant="secondary">
                    <Download size={14} />
                    下载
                  </Button>
                </a>
              ) : null}
              {selectedPath ? (
                <a href={api.files.downloadZip(selectedPath)} className="inline-flex">
                  <Button size="sm" variant="secondary">
                    <Archive size={14} />
                    打包下载
                  </Button>
                </a>
              ) : null}
              {selectedPath && !flat.find((f) => f.path === selectedPath)?.isDir ? (
                <Button size="sm" variant="secondary" onClick={openRunDialog}>
                  <Play size={14} />
                  运行
                </Button>
              ) : null}
            </div>
          </div>
          <div className="max-h-[70vh] overflow-auto">
            {isLoading ? (
              <div className="p-4 text-sm text-[var(--text-muted)]">加载中...</div>
            ) : filtered.length === 0 ? (
              <div className="p-4 text-sm text-[var(--text-muted)]">暂无文件</div>
            ) : (
              filtered.map((node) => (
                <button
                  key={node.path}
                  className={cn(
                    'block w-full truncate px-3 py-1.5 text-left text-sm hover:bg-[var(--bg-hover)]',
                    selectedPath === node.path && 'bg-[var(--bg-tertiary)]',
                  )}
                  style={{ paddingLeft: 12 + node.depth * 14 }}
                  onClick={() => setSelectedPath(node.path)}
                  title={node.path}
                >
                  {node.isDir ? '📁 ' : '📄 '}
                  {node.name}
                </button>
              ))
            )}
          </div>
        </Card>

        <Card className="overflow-hidden">
          <div className="flex items-center justify-between gap-2 border-b border-[var(--border)] px-4 py-2 text-sm text-[var(--text-muted)]">
            <span className="truncate">{selectedPath || '未选择文件'}{dirty ? ' · 未保存' : ''}</span>
            {selectedPath && !flat.find((f) => f.path === selectedPath)?.isDir ? (
              <Button
                size="sm"
                variant="ghost"
                onClick={async () => {
                  await copyToClipboard(content)
                  setMessage('内容已复制')
                }}
              >
                <Copy size={14} />
              </Button>
            ) : null}
          </div>
          <div className="h-[70vh]">
            {loadingContent ? (
              <div className="p-4 text-sm text-[var(--text-muted)]">加载内容...</div>
            ) : selectedPath && !flat.find((f) => f.path === selectedPath)?.isDir ? (
              <Editor
                height="100%"
                theme={theme === 'light' ? 'light' : 'vs-dark'}
                language={guessLanguage(selectedPath)}
                value={content}
                onChange={(v) => {
                  setContent(v || '')
                  setDirty(true)
                }}
                options={{
                  minimap: { enabled: false },
                  fontSize: 13,
                  wordWrap: 'on',
                }}
              />
            ) : (
              <div className="p-4 text-sm text-[var(--text-muted)]">
                {selectedPath ? '已选择目录，可在此目录下新建文件或上传' : '从左侧选择文件开始编辑'}
              </div>
            )}
          </div>
        </Card>
      </div>

      <Modal
        open={renameOpen}
        title="重命名"
        onClose={() => setRenameOpen(false)}
        footer={
          <>
            <Button variant="secondary" onClick={() => setRenameOpen(false)}>
              取消
            </Button>
            <Button onClick={renameSelected}>确定</Button>
          </>
        }
      >
        <Input value={renameValue} onChange={(e) => setRenameValue(e.target.value)} />
      </Modal>

      <Modal
        open={pathOpOpen}
        title={pathOpMode === 'move' ? '移动到' : '复制到'}
        onClose={() => setPathOpOpen(false)}
        footer={
          <>
            <Button variant="secondary" onClick={() => setPathOpOpen(false)}>
              取消
            </Button>
            <Button onClick={confirmPathOp}>确定</Button>
          </>
        }
      >
        <div className="space-y-2">
          <div className="text-xs text-[var(--text-muted)]">源路径: {selectedPath}</div>
          <Input value={pathOpTarget} onChange={(e) => setPathOpTarget(e.target.value)} placeholder="目标路径" />
        </div>
      </Modal>

      <Modal
        open={runOpen}
        title="运行脚本"
        onClose={() => setRunOpen(false)}
        footer={
          <>
            <Button variant="secondary" onClick={() => setRunOpen(false)}>
              取消
            </Button>
            <Button onClick={confirmRun}>
              <Play size={14} />
              去终端运行
            </Button>
          </>
        }
      >
        <div className="space-y-3">
          <div className="text-sm text-[var(--text-secondary)]">{selectedPath}</div>
          <Select value={runPlugin} onChange={(e) => {
            const plugin = e.target.value
            setRunPlugin(plugin)
            setRunVersion(pluginVersions[plugin]?.[0] || '')
          }}>
            <option value="">不指定运行时</option>
            {Object.keys(pluginVersions).map((p) => (
              <option key={p} value={p}>{p}</option>
            ))}
          </Select>
          {runPlugin ? (
            <Select value={runVersion} onChange={(e) => setRunVersion(e.target.value)}>
              {(pluginVersions[runPlugin] || []).map((v) => (
                <option key={v} value={v}>{v}</option>
              ))}
            </Select>
          ) : null}
        </div>
      </Modal>
    </div>
  )
}
