import { useCallback, useEffect, useRef, useState, type DragEvent } from 'react';

import { getDndApi } from '../shared/dndApi';

const AUDIO_EXTENSIONS = new Set(['.mp3', '.wav', '.ogg', '.m4a', '.aac']);
const MATERIAL_IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp']);
const PREVIEW_EXTENSIONS = new Set([
  '.png',
  '.jpg',
  '.jpeg',
  '.webp',
  '.gif',
  '.bmp',
  '.mp4',
  '.webm',
  '.mov',
]);

function fileExtension(filePath: string): string {
  const dot = filePath.lastIndexOf('.');
  return dot >= 0 ? filePath.slice(dot).toLowerCase() : '';
}

export type DroppedFileEntry = { path: string; name: string };

export type SceneMediaDropRejectReason = 'unsupported' | 'no_path';

export type SceneMediaDropRejected = { name: string; reason: SceneMediaDropRejectReason };

export function getDroppedFilePaths(e: DragEvent): string[] {
  return getDroppedFileEntries(e)
    .map((entry) => entry.path)
    .filter((path) => path.length > 0);
}

export function getDroppedFileEntries(e: DragEvent): DroppedFileEntry[] {
  const files = e.dataTransfer?.files;
  if (!files?.length) return [];
  const getPathForFile = getDndApi().getPathForFile;
  return Array.from(files).map((file) => ({
    path: getPathForFile(file),
    name: file.name || 'file',
  }));
}

export function filterAudioFilePaths(paths: string[]): string[] {
  return paths.filter((path) => AUDIO_EXTENSIONS.has(fileExtension(path)));
}

export function filterMaterialImagePaths(paths: string[]): string[] {
  return paths.filter((path) => MATERIAL_IMAGE_EXTENSIONS.has(fileExtension(path)));
}

export function pickFirstMaterialImagePath(paths: string[]): string | null {
  for (const path of paths) {
    if (MATERIAL_IMAGE_EXTENSIONS.has(fileExtension(path))) return path;
  }
  return null;
}

export function pickFirstPreviewFilePath(paths: string[]): string | null {
  for (const path of paths) {
    if (PREVIEW_EXTENSIONS.has(fileExtension(path))) return path;
  }
  return null;
}

export function isPreviewMediaPath(filePath: string): boolean {
  return PREVIEW_EXTENSIONS.has(fileExtension(filePath));
}

export function sceneTitleFromMediaPath(filePath: string): string {
  const base = filePath.split(/[/\\]/).pop() ?? filePath;
  const dot = base.lastIndexOf('.');
  const title = (dot > 0 ? base.slice(0, dot) : base).trim();
  return title.length > 0 ? title : 'Новая сцена';
}

export function partitionSceneMediaDrops(entries: DroppedFileEntry[]): {
  accepted: DroppedFileEntry[];
  rejected: SceneMediaDropRejected[];
} {
  const accepted: DroppedFileEntry[] = [];
  const rejected: SceneMediaDropRejected[] = [];
  for (const entry of entries) {
    if (!entry.path) {
      rejected.push({ name: entry.name, reason: 'no_path' });
      continue;
    }
    if (!isPreviewMediaPath(entry.path)) {
      rejected.push({ name: entry.name, reason: 'unsupported' });
      continue;
    }
    accepted.push(entry);
  }
  return { accepted, rejected };
}

function dragHasFiles(e: DragEvent): boolean {
  const types = Array.from(e.dataTransfer?.types ?? []);
  // Внутренний drag карточки сцены (в т.ч. с превью-картинкой) не должен считаться файловым.
  if (types.some((t) => t === 'application/x-dnd-scene-id')) return false;
  return types.includes('Files');
}

type UseFileDropZoneOptions = {
  disabled?: boolean;
  /** Синхронная блокировка (например, идёт reorder списка сцен). */
  isBlocked?: () => boolean;
  onDropPaths?: (paths: string[]) => void;
  onDropEntries?: (entries: DroppedFileEntry[]) => void;
  filterPaths?: (paths: string[]) => string[];
};

export function useFileDropZone({
  disabled = false,
  isBlocked,
  onDropPaths,
  onDropEntries,
  filterPaths,
}: UseFileDropZoneOptions) {
  const [dragOver, setDragOver] = useState(false);
  const depthRef = useRef(0);

  const blocked = useCallback(() => disabled || Boolean(isBlocked?.()), [disabled, isBlocked]);

  useEffect(() => {
    if (!blocked()) return;
    depthRef.current = 0;
    setDragOver(false);
  }, [blocked]);

  const onDragEnter = useCallback(
    (e: DragEvent) => {
      if (blocked() || !dragHasFiles(e)) return;
      e.preventDefault();
      e.stopPropagation();
      depthRef.current += 1;
      setDragOver(true);
    },
    [blocked],
  );

  const onDragLeave = useCallback(
    (e: DragEvent) => {
      if (blocked()) return;
      e.preventDefault();
      e.stopPropagation();
      depthRef.current = Math.max(0, depthRef.current - 1);
      if (depthRef.current === 0) setDragOver(false);
    },
    [blocked],
  );

  const onDragOver = useCallback(
    (e: DragEvent) => {
      if (blocked() || !dragHasFiles(e)) return;
      e.preventDefault();
      e.stopPropagation();
      e.dataTransfer.dropEffect = 'copy';
    },
    [blocked],
  );

  const onDrop = useCallback(
    (e: DragEvent) => {
      if (blocked() || !dragHasFiles(e)) return;
      e.preventDefault();
      e.stopPropagation();
      depthRef.current = 0;
      setDragOver(false);
      if (onDropEntries) {
        const entries = getDroppedFileEntries(e);
        if (entries.length === 0) return;
        onDropEntries(entries);
        return;
      }
      const raw = getDroppedFilePaths(e);
      const paths = filterPaths ? filterPaths(raw) : raw;
      if (paths.length === 0) return;
      onDropPaths?.(paths);
    },
    [blocked, filterPaths, onDropEntries, onDropPaths],
  );

  return { dragOver, onDragEnter, onDragLeave, onDragOver, onDrop };
}
