import type { JSX } from 'preact';
import { useCallback, useRef, useState } from 'preact/hooks';
import { cn } from './cn';
import { Button } from './Button';
import { getI18n } from '../../core/i18n';

export interface FileDropzoneProps {
  accept?: string;
  multiple?: boolean;
  label?: string;
  hint?: string;
  className?: string;
  /** Condenses the prompt and browse action into one horizontal editor row. */
  compact?: boolean;
  /** Semantic context used by host integrations to distinguish canvas uploads from form media fields. */
  uploadContext?: 'canvas-upload' | 'media-input' | string;
  /** Optional custom picker. Return `false` to fall back to the native file input. */
  onBrowse?: () => void | boolean | Promise<void | boolean>;
  onFiles: (files: File[]) => void;
}

function dragEventHasFiles(event: DragEvent): boolean {
  const transfer = event.dataTransfer;
  if (!transfer) return false;

  try {
    if (Array.from(transfer.types ?? []).includes('Files')) return true;
  } catch {}

  try {
    return Array.from(transfer.items ?? []).some((item) => item.kind === 'file');
  } catch {
    return false;
  }
}

function extractDroppedFiles(transfer: DataTransfer | null): File[] {
  if (!transfer) return [];

  const files = Array.from(transfer.files ?? []).filter(Boolean);
  if (files.length) return files;

  try {
    return Array.from(transfer.items ?? [])
      .filter((item) => item.kind === 'file')
      .map((item) => item.getAsFile())
      .filter((file): file is File => Boolean(file));
  } catch {
    return [];
  }
}

function fileMatchesAccept(file: File, accept?: string): boolean {
  const raw = String(accept ?? '').trim();
  if (!raw) return true;

  const tokens = raw
    .split(',')
    .map((item) => item.trim().toLowerCase())
    .filter(Boolean);
  if (!tokens.length) return true;

  const name = String(file.name ?? '').toLowerCase();
  const type = String(file.type ?? '').toLowerCase();

  return tokens.some((token) => {
    if (token.endsWith('/*')) return type.startsWith(token.slice(0, -1));
    if (token.startsWith('.')) return name.endsWith(token);
    return type === token;
  });
}

/**
 * FileDropzone (core)
 * - Drag & drop + browse button
 * - Pure UI component, reusable for images, SVG, etc.
 */
export function FileDropzone({ accept, multiple, label = getI18n().t('fileDropzone.label', 'Click or drop files here'), hint, className, compact = false, uploadContext, onBrowse, onFiles }: FileDropzoneProps) {
  const inputRef = useRef<HTMLInputElement | null>(null);
  const dragDepthRef = useRef(0);
  const [drag, setDrag] = useState(false);

  const pick = useCallback(() => {
    if (typeof onBrowse === 'function') {
      try {
        const result = onBrowse();
        if (result && typeof (result as Promise<void | boolean>).then === 'function') {
          void (result as Promise<void | boolean>).then((handled) => {
            if (handled === false) inputRef.current?.click();
          });
          return;
        }
        if (result !== false) return;
      } catch {
        // Fall back to the native file input if the custom picker fails synchronously.
      }
    }
    inputRef.current?.click();
  }, [onBrowse]);

  const handleFiles = useCallback(
    (files: File[] | FileList | null) => {
      const list = Array.from(files ?? []).filter((file) => fileMatchesAccept(file, accept));
      if (!list.length) return;
      onFiles(multiple ? list : list.slice(0, 1));
    },
    [accept, multiple, onFiles],
  );

  const markCopyDrop = (event: DragEvent) => {
    event.preventDefault();
    event.stopPropagation();
    if (event.dataTransfer) {
      event.dataTransfer.dropEffect = 'copy';
    }
  };

  const onDragEnter = useCallback((event: DragEvent) => {
    if (!dragEventHasFiles(event)) return;
    markCopyDrop(event);
    dragDepthRef.current += 1;
    setDrag(true);
  }, []);

  const onDragOver = useCallback((event: DragEvent) => {
    if (!dragEventHasFiles(event)) return;
    markCopyDrop(event);
    setDrag(true);
  }, []);

  const onDragLeave = useCallback((event: DragEvent) => {
    if (!dragEventHasFiles(event)) return;
    event.preventDefault();
    event.stopPropagation();
    dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
    if (dragDepthRef.current === 0) setDrag(false);
  }, []);

  const onDrop = useCallback(
    (event: DragEvent) => {
      if (!dragEventHasFiles(event)) return;
      markCopyDrop(event);
      dragDepthRef.current = 0;
      setDrag(false);
      handleFiles(extractDroppedFiles(event.dataTransfer));
    },
    [handleFiles],
  );

  const onInputChange = useCallback(
    (event: JSX.TargetedEvent<HTMLInputElement, Event>) => {
      const input = event.currentTarget;
      handleFiles(input.files);
      // Allows picking the same file again after removing it.
      input.value = '';
    },
    [handleFiles],
  );

  return (
    <div
      className={cn('ask-border ask-border-dashed ask-border-borderSubtle ask-bg-surfaceAlt ask-transition-colors', compact ? 'ask-rounded-lg ask-p-2.5 ask-text-left' : 'ask-rounded-[16px] ask-p-4 ask-text-center', drag && 'ask-border-primary ask-bg-primary/5', className)}
      data-asuka-upload-context={uploadContext}
      onClick={(event) => {
        const target = event.target as HTMLElement | null;
        if (target?.closest?.('button,input')) return;
        pick();
      }}
      onDragEnter={onDragEnter as any}
      onDrop={onDrop as any}
      onDragOver={onDragOver as any}
      onDragLeave={onDragLeave as any}
    >
      <div className={cn(compact && 'ask-flex ask-items-center ask-justify-between ask-gap-3')}>
        <div className="ask-min-w-0">
          <div className="ask-text-[11px] ask-font-medium">{label}</div>
          {hint ? <div className={cn('ask-text-[10px] ask-text-textMuted', compact ? 'ask-mt-0.5 ask-line-clamp-2' : 'ask-mt-1')}>{hint}</div> : null}
        </div>

        <div className={cn('ask-flex ask-shrink-0', compact ? 'ask-justify-end' : 'ask-mt-3 ask-justify-center')}>
          <Button size="sm" onClick={pick}>
            {getI18n().t('fileDropzone.browseFile', 'Browse file')}
          </Button>
        </div>
      </div>

      <input ref={inputRef} type="file" accept={accept} multiple={multiple} className="ask-hidden" onChange={onInputChange} />
    </div>
  );
}
