/**
 * Drop items: unified abstraction for files dropped from OS.
 *
 * Two paths are handled here:
 *   1. Electron — File objects carry a `.path` (absolute OS path), so we just
 *      pass paths to `/api/workspaces/[id]/fs/import` and let the server do
 *      `fs.cpSync` directly. Folders are handled recursively by the server.
 *   2. Web browser — File objects don't carry `.path`. We use the
 *      `webkitGetAsEntry()` API to read the dropped tree (including nested
 *      folders) and produce a flat list of `{ relativePath, blob }` items
 *      which are uploaded via multipart to `/api/workspaces/[id]/fs/upload`.
 *
 * NOTE: `dataTransfer.items` is only readable inside the `drop` event handler
 * (browser security). Always extract entries synchronously first, then await.
 */

export interface DropItem {
  /** Path relative to the dropped root (e.g. "myfolder/sub/a.txt" or "a.txt"). */
  relativePath: string
  /** Web path: blob to upload via multipart. */
  blob?: Blob
  /** Electron path: absolute OS path; server will read directly. */
  absPath?: string
  /** Size in bytes (0 if unknown for Electron folder roots). */
  size: number
}

export interface ReadDropResult {
  items: DropItem[]
  /** Number of entries skipped by the filter (`.git`, `node_modules`, etc.). */
  filteredCount: number
  /** True iff every item is an absolute path (Electron fast path). */
  isElectronPath: boolean
}

/** Top-level names that are skipped by default. Configurable in the future. */
const DEFAULT_FILTER = new Set<string>(['.git', 'node_modules', '.DS_Store', '.next', 'dist', '.turbo'])

function isFiltered(name: string): boolean {
  return DEFAULT_FILTER.has(name)
}

/**
 * Read items from a drop event's DataTransfer.
 *
 * IMPORTANT: Must be called synchronously inside the `drop` handler.
 * The function will collect entry references synchronously, then resolve
 * blobs asynchronously.
 */
export async function readDropItems(dt: DataTransfer): Promise<ReadDropResult> {
  // ── Path 1: Electron — every File has a `.path` field ──
  // We probe `dt.files` first because in Electron the path is set on every File.
  const files = dt.files
  if (files && files.length > 0) {
    const electronItems: DropItem[] = []
    let allHavePath = true
    for (let i = 0; i < files.length; i++) {
      const f = files[i] as File & { path?: string }
      if (!f.path) {
        allHavePath = false
        break
      }
      if (isFiltered(f.name)) continue
      electronItems.push({
        relativePath: f.name,
        absPath: f.path,
        size: f.size,
      })
    }
    if (allHavePath) {
      return {
        items: electronItems,
        filteredCount: files.length - electronItems.length,
        isElectronPath: true,
      }
    }
  }

  // ── Path 2: Web — use webkitGetAsEntry to walk the tree ──
  // Synchronously collect entry references first; awaiting later is safe.
  const entries: FileSystemEntry[] = []
  let topLevelFiltered = 0
  if (dt.items && dt.items.length > 0) {
    for (let i = 0; i < dt.items.length; i++) {
      const item = dt.items[i]
      // `webkitGetAsEntry` is non-standard but supported in all modern browsers
      // (including Safari). The standardized `getAsEntry` is still rolling out.
      const entry = (item as DataTransferItem & {
        webkitGetAsEntry?: () => FileSystemEntry | null
      }).webkitGetAsEntry?.()
      if (!entry) continue
      if (isFiltered(entry.name)) {
        topLevelFiltered++
        continue
      }
      entries.push(entry)
    }
  }

  const out: DropItem[] = []
  let nestedFiltered = 0
  for (const entry of entries) {
    nestedFiltered += await walkEntry(entry, '', out)
  }

  return {
    items: out,
    filteredCount: topLevelFiltered + nestedFiltered,
    isElectronPath: false,
  }
}

/**
 * Recursively walk a FileSystemEntry, pushing files into `out`.
 * Returns the number of entries skipped by the filter.
 */
async function walkEntry(
  entry: FileSystemEntry,
  prefix: string,
  out: DropItem[],
): Promise<number> {
  let filtered = 0

  if (entry.isFile) {
    const file = await new Promise<File>((resolve, reject) => {
      ;(entry as FileSystemFileEntry).file(resolve, reject)
    })
    out.push({
      relativePath: prefix + entry.name,
      blob: file,
      size: file.size,
    })
    return 0
  }

  if (entry.isDirectory) {
    const dir = entry as FileSystemDirectoryEntry
    const reader = dir.createReader()
    // Safari/Chrome only return up to 100 entries per `readEntries` call;
    // keep reading until we get an empty batch.
    while (true) {
      const batch = await new Promise<FileSystemEntry[]>((resolve, reject) => {
        reader.readEntries(resolve, reject)
      })
      if (!batch.length) break
      for (const child of batch) {
        if (isFiltered(child.name)) {
          filtered++
          continue
        }
        filtered += await walkEntry(child, prefix + entry.name + '/', out)
      }
    }
  }

  return filtered
}

/** Total bytes across all DropItems (skips items with unknown size). */
export function totalDropSize(items: DropItem[]): number {
  let total = 0
  for (const it of items) total += it.size || 0
  return total
}

/** Format bytes for human display (e.g. "12.3 MB"). */
export function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`
  return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`
}

export interface UploadResult {
  ok: boolean
  imported?: string[]
  failed?: { path: string; reason: string }[]
  error?: string
}

/**
 * Upload Web DropItems (blobs) to the server via multipart/form-data.
 * Items must have `blob` set (i.e. came from the Web webkitGetAsEntry path).
 *
 * Server endpoint: POST /api/workspaces/[workspaceId]/fs/upload
 */
export async function uploadDropItems(
  workspaceId: string,
  items: DropItem[],
  destinationFolder: string,
): Promise<UploadResult> {
  const blobItems = items.filter(it => it.blob)
  if (!blobItems.length) return { ok: true, imported: [], failed: [] }

  const fd = new FormData()
  fd.set('destinationFolder', destinationFolder || '.')
  fd.set('manifest', JSON.stringify(
    blobItems.map((it, idx) => ({
      index: idx,
      relativePath: it.relativePath,
      size: it.size,
    })),
  ))
  blobItems.forEach((it, idx) => {
    // Use the basename as the multipart filename — relativePath travels in manifest.
    const basename = it.relativePath.split('/').pop() || `file_${idx}`
    fd.append(`file_${idx}`, it.blob!, basename)
  })

  const res = await fetch(`/api/workspaces/${workspaceId}/fs/upload`, {
    method: 'POST',
    body: fd,
  })
  if (!res.ok) {
    const text = await res.text().catch(() => '')
    return { ok: false, error: text || `HTTP ${res.status}` }
  }
  return await res.json() as UploadResult
}
