import { computed, ref } from 'vue'
import {
  createDocFolder,
  createDocTextFile,
  deleteDocNode,
  fetchDocBlob,
  fetchDocNodes,
  replaceDocBlob,
  updateDocNode,
  uploadDocFile,
} from '../utils/docsApi'

const MAX_FILE_SIZE = 1024 * 1024 * 1024
const MAX_NODES = 5000

function getExtension(name = '') {
  const parts = String(name).split('.')
  return parts.length > 1 ? parts.pop().toLowerCase() : ''
}

function isTextExtension(ext) {
  const textExtensions = [
    'md', 'markdown', 'txt', 'json', 'js', 'jsx', 'ts', 'tsx',
    'css', 'scss', 'less', 'html', 'htm', 'py', 'vue', 'xml',
    'yaml', 'yml', 'csv', 'log', 'sql', 'toml', 'ini', 'cfg',
    'conf', 'sh', 'bat', 'ps1', 'java', 'c', 'cpp', 'h', 'hpp',
    'go', 'rs', 'swift', 'kt', 'rb', 'php', 'pl', 'r', 'scala',
    'gradle', 'properties', 'env', 'gitignore', 'dockerfile'
  ]
  return textExtensions.includes(ext)
}

function isBinaryExtension(ext) {
  const binaryExtensions = [
    'exe', 'dll', 'so', 'dylib', 'bin', 'dat', 'obj', 'o', 'a',
    'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp',
    'pdf', 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz',
    'png', 'jpg', 'jpeg', 'gif', 'bmp', 'ico', 'webp', 'svg',
    'mp3', 'mp4', 'wav', 'avi', 'mov', 'mkv', 'flv', 'wmv',
    'ttf', 'otf', 'woff', 'woff2', 'eot',
    'class', 'pyc', 'pyo', 'jar', 'war', 'ear',
    'db', 'sqlite', 'mdb', 'accdb',
    'pem', 'key', 'crt', 'cer', 'p12', 'pfx', 'jks',
    'msg', 'eml', 'pst', 'ost',
    'dwg', 'dxf', 'step', 'stl', 'obj', 'fbx', '3ds', 'blend'
  ]
  return binaryExtensions.includes(ext.toLowerCase())
}

function inferMimeType(name, fallback = '') {
  const ext = getExtension(name)
  const map = {
    avi: 'video/x-msvideo',
    md: 'text/markdown',
    markdown: 'text/markdown',
    mkv: 'video/x-matroska',
    mov: 'video/quicktime',
    mp4: 'video/mp4',
    txt: 'text/plain',
    json: 'application/json',
    js: 'text/javascript',
    jsx: 'text/javascript',
    ts: 'text/typescript',
    tsx: 'text/typescript',
    css: 'text/css',
    html: 'text/html',
    htm: 'text/html',
    py: 'text/x-python',
    vue: 'text/plain',
    xml: 'application/xml',
    yaml: 'text/yaml',
    yml: 'text/yaml',
    csv: 'text/csv',
    log: 'text/plain',
    sql: 'text/plain',
    toml: 'text/plain',
    ini: 'text/plain',
    cfg: 'text/plain',
    conf: 'text/plain',
    sh: 'text/plain',
    bat: 'text/plain',
    ps1: 'text/plain',
    jpg: 'image/jpeg',
    jpeg: 'image/jpeg',
    flv: 'video/x-flv',
    m4v: 'video/x-m4v',
    png: 'image/png',
    gif: 'image/gif',
    webp: 'image/webp',
    svg: 'image/svg+xml',
    pdf: 'application/pdf',
    ogg: 'video/ogg',
    ogv: 'video/ogg',
    webm: 'video/webm',
    wmv: 'video/x-ms-wmv'
  }
  return fallback || map[ext] || 'application/octet-stream'
}

function isTextFile(record) {
  const ext = getExtension(record?.name)
  if (isBinaryExtension(ext)) return false
  const mime = String(record?.mimeType || '')
  return isTextExtension(ext) || mime.startsWith('text/') || mime.includes('json') || mime.includes('xml')
}

function buildTree(records) {
  const map = new Map()
  const roots = []
  for (const record of records) {
    map.set(record.id, {
      ...record,
      children: record.type === 'folder' ? [] : undefined
    })
  }
  for (const record of records) {
    const current = map.get(record.id)
    if (!record.parentId) {
      roots.push(current)
      continue
    }
    const parent = map.get(record.parentId)
    if (parent?.type === 'folder') parent.children.push(current)
    else roots.push(current)
  }
  const sorter = (a, b) => {
    if (a.type !== b.type) return a.type === 'folder' ? -1 : 1
    return a.name.localeCompare(b.name, 'zh-CN', { sensitivity: 'base' })
  }
  const sortChildren = (nodes) => {
    nodes.sort(sorter)
    for (const node of nodes) {
      if (node.type === 'folder' && Array.isArray(node.children)) sortChildren(node.children)
    }
  }
  sortChildren(roots)
  return roots
}

function findNode(nodes, id) {
  for (const node of nodes) {
    if (node.id === id) return node
    if (node.type === 'folder') {
      const found = findNode(node.children || [], id)
      if (found) return found
    }
  }
  return null
}

function getPath(nodes, id, path = []) {
  for (const node of nodes) {
    if (node.id === id) return [...path, node]
    if (node.type === 'folder') {
      const found = getPath(node.children || [], id, [...path, node])
      if (found) return found
    }
  }
  return null
}

function estimateRecordSize(record) {
  if (typeof record?.size === 'number') return record.size
  if (typeof record?.content === 'string') return new Blob([record.content]).size
  if (typeof record?.previewText === 'string') return new Blob([record.previewText]).size
  return 0
}

function cloneNode(node) {
  return JSON.parse(JSON.stringify(node))
}

let singleton = null

export function useFileSystem() {
  if (singleton) return singleton

  const records = ref([])
  const selectedId = ref(null)
  const expandedIds = ref(new Set())
  const clipboard = ref(null)
  const contextMenu = ref(null)
  const error = ref(null)
  const loading = ref(false)
  const blobCache = new Map()

  const tree = computed(() => buildTree(records.value))
  const stats = computed(() => {
    let fileCount = 0
    let folderCount = 0
    let usedBytes = 0
    for (const record of records.value) {
      if (record.type === 'folder') folderCount += 1
      else fileCount += 1
      usedBytes += estimateRecordSize(record)
    }
    return { fileCount, folderCount, usedBytes }
  })

  function clearBlobCache(id = null) {
    if (id) {
      blobCache.delete(id)
      return
    }
    blobCache.clear()
  }

  function upsertRecord(nextRecord) {
    const index = records.value.findIndex((item) => item.id === nextRecord.id)
    if (index === -1) records.value = [...records.value, nextRecord]
    else {
      const next = [...records.value]
      next[index] = nextRecord
      records.value = next
    }
    return nextRecord
  }

  // --- Async operations with unified error handling ---

  async function load() {
    loading.value = true; records.value = []
    try { records.value = await fetchDocNodes() }
    catch (err) { error.value = err?.message || '读取文档空间失败，请稍后重试' }
    finally { loading.value = false }
  }

  async function createFile(parentId, name, content = '') {
    if (records.value.length >= MAX_NODES) {
      error.value = `文件数量不能超过 ${MAX_NODES} 个`
      return false
    }
    try {
      const node = await createDocTextFile(name, parentId || null, content)
      upsertRecord(node); selectedId.value = node.id
      if (parentId) { expandedIds.value = new Set([...expandedIds.value, parentId]) }
      return true
    } catch (err) { error.value = err?.message || '创建文件失败'; return false }
  }

  async function updateFile(id, nextValue, options = {}) {
    const file = records.value.find((item) => item.id === id && item.type === 'file')
    if (!file) return false
    try {
      const node = nextValue instanceof Blob
        ? await replaceDocBlob(id, nextValue instanceof File ? nextValue : new File([nextValue], options.name || file.name, { type: options.mimeType || nextValue.type || '' }))
        : await updateDocNode(id, { name: options.name || file.name, content: String(options.content ?? nextValue) })
      upsertRecord(node); clearBlobCache(id); return true
    } catch (err) { error.value = err?.message || '保存文件失败'; return false }
  }

  async function createFolder(parentId, name) {
    if (records.value.length >= MAX_NODES) {
      error.value = `目录项数量不能超过 ${MAX_NODES} 个`
      return false
    }
    try {
      const node = await createDocFolder(name, parentId || null)
      upsertRecord(node); selectedId.value = node.id
      if (parentId) expandedIds.value = new Set([...expandedIds.value, parentId])
      return true
    } catch (err) { error.value = err?.message || '创建文件夹失败'; return false }
  }

  async function rename(id, newName) {
    const node = records.value.find((item) => item.id === id)
    if (!node) return false
    try {
      const updated = await updateDocNode(id, { name: newName })
      upsertRecord(updated); clearBlobCache(id); return true
    } catch (err) { error.value = err?.message || '重命名失败'; return false }
  }

  function collectDescendantIds(id) {
    const ids = new Set([id])
    let changed = true
    while (changed) {
      changed = false
      for (const record of records.value) {
        if (record.parentId && ids.has(record.parentId) && !ids.has(record.id)) {
          ids.add(record.id)
          changed = true
        }
      }
    }
    return [...ids]
  }

  async function remove(id) {
    const ids = new Set(collectDescendantIds(id))
    try {
      await deleteDocNode(id)
      records.value = records.value.filter((item) => !ids.has(item.id))
      if (selectedId.value && ids.has(selectedId.value)) selectedId.value = null
      if (clipboard.value?.nodeId && ids.has(clipboard.value.nodeId)) clipboard.value = null
      if (clipboard.value?.node?.id && ids.has(clipboard.value.node.id)) clipboard.value = null
      ids.forEach((currentId) => clearBlobCache(currentId))
      error.value = null
      return true
    } catch (err) {
      error.value = err instanceof Error && err.message ? err.message : '删除文件失败'
      return false
    }
  }

  function select(id) {
    selectedId.value = id
  }

  function toggleFolder(id) {
    const node = records.value.find((item) => item.id === id)
    if (!node || node.type !== 'folder') return
    const next = new Set(expandedIds.value)
    if (next.has(id)) next.delete(id)
    else next.add(id)
    expandedIds.value = next
  }

  function copy(id) {
    const node = findNode(tree.value, id)
    if (!node) return
    clipboard.value = { mode: 'copy', node: cloneNode(node) }
  }

  function cut(id) {
    const node = records.value.find((item) => item.id === id)
    if (!node) return
    clipboard.value = { mode: 'cut', nodeId: node.id }
  }

  function isDescendantOf(sourceId, targetParentId) {
    let current = records.value.find((item) => item.id === targetParentId)
    while (current) {
      if (current.parentId === sourceId) return true
      current = current.parentId ? records.value.find((item) => item.id === current.parentId) : null
    }
    return false
  }

  async function duplicateNode(node, targetParentId) {
    let created
    if (node.type === 'folder') {
      created = await createDocFolder(node.name, targetParentId)
      upsertRecord(created)
      for (const child of node.children || []) {
        await duplicateNode(child, created.id)
      }
      return created
    }

    if (node.storageKind === 'blob') {
      const blob = await getFileBlob(node)
      const upload = new File([blob], node.name, { type: node.mimeType || blob.type || inferMimeType(node.name) })
      created = await uploadDocFile(upload, targetParentId)
    } else {
      created = await createDocTextFile(node.name, targetParentId, node.content || node.previewText || '')
    }
    upsertRecord(created)
    return created
  }

  async function paste(targetParentId) {
    if (!clipboard.value) return
    try {
      if (clipboard.value.mode === 'cut') {
        const node = records.value.find((item) => item.id === clipboard.value.nodeId)
        if (!node) {
          clipboard.value = null
          return
        }
        if (node.id === targetParentId || (targetParentId && isDescendantOf(node.id, targetParentId))) {
          error.value = '不能移动到自身或子目录中'
          return
        }
        const updated = await updateDocNode(node.id, { parentId: targetParentId || null })
        upsertRecord(updated)
        clipboard.value = null
        error.value = null
        return
      }
      const source = clipboard.value.node
      if (!source) return
      if (records.value.length >= MAX_NODES) {
        error.value = `目录项数量不能超过 ${MAX_NODES} 个`
        return
      }
      await duplicateNode(source, targetParentId || null)
      error.value = null
    } catch (err) {
      error.value = err instanceof Error && err.message ? err.message : '粘贴失败'
    }
  }

  function canPaste() {
    return clipboard.value !== null
  }

  function clearClipboard() {
    clipboard.value = null
  }

  function getSelectedNode() {
    return selectedId.value ? findNode(tree.value, selectedId.value) : null
  }

  function getBreadcrumbPath(id) {
    return getPath(tree.value, id) || []
  }

  function showContextMenu(x, y, node) {
    contextMenu.value = { x, y, node }
  }

  function hideContextMenu() {
    contextMenu.value = null
  }

  function getFileIcon(name) {
    const ext = getExtension(name)
    const iconMap = {
      avi: 'video',
      md: 'markdown',
      markdown: 'markdown',
      mkv: 'video',
      mov: 'video',
      mp4: 'video',
      txt: 'text',
      json: 'json',
      js: 'javascript',
      jsx: 'javascript',
      ts: 'typescript',
      tsx: 'typescript',
      css: 'css',
      html: 'html',
      htm: 'html',
      py: 'python',
      vue: 'vue',
      xml: 'xml',
      yaml: 'yaml',
      yml: 'yaml',
      csv: 'csv',
      log: 'log',
      sql: 'sql',
      jpg: 'image',
      jpeg: 'image',
      png: 'image',
      gif: 'image',
      flv: 'video',
      m4v: 'video',
      webp: 'image',
      svg: 'image',
      ogg: 'video',
      ogv: 'video',
      pdf: 'pdf',
      doc: 'word',
      docx: 'word',
      ppt: 'ppt',
      pptx: 'ppt',
      webm: 'video',
      wmv: 'video',
      xls: 'excel',
      xlsx: 'excel',
      zip: 'zip'
    }
    return iconMap[ext] || 'file'
  }

  async function uploadFiles(files, parentId = null) {
    const source = Array.from(files || [])
    if (source.length === 0) return { success: 0, failed: [] }
    const failed = []
    let success = 0
    for (const file of source) {
      if (file.size > MAX_FILE_SIZE) {
        failed.push({ name: file.name, reason: '单个文件不能超过 1GB' })
        continue
      }
      try {
        const node = await uploadDocFile(file, parentId)
        upsertRecord(node)
        success += 1
      } catch (err) {
        failed.push({
          name: file.name,
          reason: err instanceof Error && err.message ? err.message : '上传文件失败',
        })
      }
    }
    if (success > 0 && parentId) {
      const next = new Set(expandedIds.value)
      next.add(parentId)
      expandedIds.value = next
    }
    return { success, failed }
  }

  async function getFileBlob(node) {
    if (!node || node.type !== 'file') return null
    if (blobCache.has(node.id)) return blobCache.get(node.id)
    if (node.storageKind === 'text' && typeof node.content === 'string' && node.content && !node.isTruncatedPreview) {
      const blob = new Blob([node.content], { type: inferMimeType(node.name, node.mimeType) })
      blobCache.set(node.id, blob)
      return blob
    }
    const blob = await fetchDocBlob(node.id)
    blobCache.set(node.id, blob)
    return blob
  }

  singleton = {
    tree,
    selectedId,
    expandedIds,
    clipboard,
    contextMenu,
    error,
    loading,
    stats,
    load,
    createFile,
    updateFile,
    createFolder,
    rename,
    remove,
    select,
    toggleFolder,
    copy,
    cut,
    paste,
    canPaste,
    clearClipboard,
    getSelectedNode,
    getBreadcrumbPath,
    showContextMenu,
    hideContextMenu,
    getFileIcon,
    getExtension,
    getFileBlob,
    isTextFile,
    uploadFiles,
    MAX_FILE_SIZE,
    MAX_NODES
  }

  return singleton
}
