type FileSystemHandleLike = FileSystemFileHandleLike | FileSystemDirectoryHandleLike;
type FileSystemFileHandleLike = { kind: "file"; getFile: () => Promise<File> };
type FileSystemDirectoryHandleLike = {
  kind: "directory";
  values?: () => AsyncIterable<FileSystemHandleLike>;
};
type WebKitFileEntryLike = {
  isFile: true;
  isDirectory: false;
  file: (
    success: (file: File) => void,
    error?: (error: DOMException) => void
  ) => void;
};
type WebKitDirectoryEntryLike = { isFile: false; isDirectory: true };
type WebKitEntryLike = WebKitFileEntryLike | WebKitDirectoryEntryLike;
type DropItemWithFileHandles = DataTransferItem & {
  getAsFileSystemHandle?: () => Promise<FileSystemHandleLike | null>;
  webkitGetAsEntry?: () => unknown;
};

/**
 * Resolve a browser file drop exactly once, preferring the standard file list
 * before progressively trying item and directory-drag compatibility APIs.
 */
export async function resolveDroppedFiles(
  dataTransfer: Pick<DataTransfer, "files" | "items">
): Promise<File[]> {
  const directFiles = Array.from(dataTransfer.files);
  if (directFiles.length > 0) return directFiles;

  const items = Array.from(dataTransfer.items).filter(
    (item) => item.kind === "file"
  ) as DropItemWithFileHandles[];
  const itemFiles = items
    .map((item) => item.getAsFile())
    .filter((file): file is File => Boolean(file));
  if (itemFiles.length > 0) return itemFiles;

  const handleFiles = await filesFromDataTransferHandles(items);
  if (handleFiles.length > 0) return handleFiles;

  return filesFromWebKitEntries(items);
}

async function filesFromDataTransferHandles(
  items: DropItemWithFileHandles[]
): Promise<File[]> {
  const files: File[] = [];
  for (const item of items) {
    let handle: FileSystemHandleLike | null | undefined;
    try {
      handle = await item.getAsFileSystemHandle?.();
    } catch {
      handle = null;
    }
    if (!handle) continue;
    files.push(...await filesFromFileSystemHandle(handle));
  }
  return files;
}

async function filesFromFileSystemHandle(
  handle: FileSystemHandleLike
): Promise<File[]> {
  if (handle.kind === "file") return [await handle.getFile()];
  const values = handle.values?.();
  if (!values) return [];
  const files: File[] = [];
  for await (const child of values) {
    files.push(...await filesFromFileSystemHandle(child));
  }
  return files;
}

async function filesFromWebKitEntries(
  items: DropItemWithFileHandles[]
): Promise<File[]> {
  const entries = items.flatMap((item) => {
    const entry = item.webkitGetAsEntry?.() as unknown;
    return isWebKitEntryLike(entry) ? [entry] : [];
  });
  const files = await Promise.all(entries.map(fileFromWebKitEntry));
  return files.filter((file): file is File => Boolean(file));
}

function isWebKitEntryLike(value: unknown): value is WebKitEntryLike {
  if (!value || typeof value !== "object") return false;
  const entry = value as { isFile?: unknown; isDirectory?: unknown };
  return (
    typeof entry.isFile === "boolean" &&
    typeof entry.isDirectory === "boolean"
  );
}

function fileFromWebKitEntry(entry: WebKitEntryLike): Promise<File | null> {
  if (!entry.isFile) return Promise.resolve(null);
  return new Promise((resolve, reject) => {
    entry.file(resolve, reject);
  });
}
