'use client';

/**
 * FileUpload - 文件上传内嵌区域（拖拽 + 点击选择 + 队列 + 进度）
 *
 * 设计选择：
 * - 上传执行器（uploader）由调用方注入，组件不直接耦合 attachment API
 * - 前端先按 8 项 MIME 白名单 + 200MB 校验，失败立即标 validateError 不发请求
 * - 并发数 3：同一时刻最多 3 个 in-flight，其余排队
 * - 拖目录：DataTransfer.items 中含 `webkitGetAsEntry().isDirectory` 直接拒
 * - 单次最多 10 文件，超出截断 + toast
 * - 失败可重试（重头上传，无断点续传）
 * - 不直接弹 dialog，由 UploadDialog 包一层；本组件可独立挂在任意容器
 */

import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Loader2, Upload, X, RefreshCw } from 'lucide-react';
import { toast } from 'sonner';
import { useTranslation } from '@/hooks/useTranslation';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import {
  ALLOWED_ATTACHMENT_MIME,
  MAX_ATTACHMENT_SIZE,
  validateAttachmentClient,
} from '@/services/api/attachment';
import { ApiClientError } from '@/lib/api-client';
import type { SingleUploader, UploadQueueItem } from './types';

const MAX_FILES_PER_UPLOAD = 10;
const MAX_CONCURRENT_UPLOADS = 3;

export interface FileUploadProps {
  /** 上传单文件的执行器（调用方注入，组件内部并发控制 + 重试） */
  uploader: SingleUploader;
  /** 全部上传完成（即队列已无 queued/uploading）回调；用于父组件 invalidate 列表 */
  onAllSettled?: (items: UploadQueueItem[]) => void;
  /** 当一个文件上传成功时回调（用于增量刷新） */
  onFileSuccess?: (file: File) => void;
  /** 关闭整个上传面板（由调用方决定，比如关 dialog） */
  onRequestClose?: () => void;
  className?: string;
}

let nextLocalId = 1;
const genId = () => `upload-${Date.now()}-${nextLocalId++}`;

export function FileUpload({
  uploader,
  onAllSettled,
  onFileSuccess,
  onRequestClose,
  className,
}: FileUploadProps) {
  const { t } = useTranslation();
  const a = t.meetingAttendance.attachment;
  const [queue, setQueue] = useState<UploadQueueItem[]>([]);
  const [isDragOver, setIsDragOver] = useState(false);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const queueRef = useRef<UploadQueueItem[]>([]);
  queueRef.current = queue;

  // ─── helpers ────────────────────────────────────────────────────────────

  const errorMessage = useCallback(
    (code: string | undefined): string => {
      if (!code) return a.netError;
      switch (code) {
        case 'ATTACHMENT_TOO_LARGE':
          return a.tooLarge;
        case 'ATTACHMENT_MIME_NOT_ALLOWED':
          return a.mimeNotAllowed;
        case 'ATTACHMENT_MIME_MISMATCH':
          return a.mimeMismatch;
        case 'UPLOAD_TASK_NOT_OWNED':
          return a.notOwned;
        case 'ATTACHMENT_NOT_FOUND':
          return a.notFound;
        case 'ATTACHMENT_DELETE_FORBIDDEN':
          return a.deleteForbidden;
        default:
          return a.netError;
      }
    },
    [a],
  );

  // ─── 入队 ───────────────────────────────────────────────────────────────

  const enqueueFiles = useCallback(
    (files: File[]) => {
      if (files.length === 0) return;
      setQueue((prev) => {
        const slots = MAX_FILES_PER_UPLOAD - prev.length;
        if (slots <= 0) {
          toast.error(a.tooManyFiles);
          return prev;
        }
        const accept = files.slice(0, slots);
        if (files.length > slots) {
          toast.error(a.tooManyFiles);
        }
        const next: UploadQueueItem[] = accept.map((file) => {
          const err = validateAttachmentClient(file);
          return {
            id: genId(),
            file,
            status: err ? 'failed' : 'queued',
            progress: 0,
            loaded: 0,
            validateError: err ?? undefined,
            uploadErrorMessage: err ? errorMessage(err) : undefined,
          };
        });
        return [...prev, ...next];
      });
    },
    [a.tooManyFiles, errorMessage],
  );

  // ─── 拖拽 + file picker ────────────────────────────────────────────────

  const onDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(true);
  }, []);

  const onDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    e.stopPropagation();
    setIsDragOver(false);
  }, []);

  const onDrop = useCallback(
    (e: React.DragEvent<HTMLDivElement>) => {
      e.preventDefault();
      e.stopPropagation();
      setIsDragOver(false);

      // 拒目录上传：检查 DataTransferItemList
      const items = e.dataTransfer.items;
      if (items) {
        for (let i = 0; i < items.length; i++) {
          const item = items[i] as DataTransferItem & {
            webkitGetAsEntry?: () => { isDirectory: boolean } | null;
          };
          const entry = item.webkitGetAsEntry?.();
          if (entry?.isDirectory) {
            toast.error(a.folderNotSupported);
            return;
          }
        }
      }

      const files = Array.from(e.dataTransfer.files);
      enqueueFiles(files);
    },
    [a.folderNotSupported, enqueueFiles],
  );

  const onPickerChange = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const files = e.target.files ? Array.from(e.target.files) : [];
      enqueueFiles(files);
      // 重置 input 让同一个文件再次选择能触发 change
      if (fileInputRef.current) fileInputRef.current.value = '';
    },
    [enqueueFiles],
  );

  // ─── 单文件上传 ─────────────────────────────────────────────────────────

  const startUpload = useCallback(
    async (itemId: string) => {
      const item = queueRef.current.find((q) => q.id === itemId);
      if (!item) return;

      const ac = new AbortController();
      setQueue((prev) =>
        prev.map((q) =>
          q.id === itemId
            ? { ...q, status: 'uploading', progress: 0, loaded: 0, abortController: ac, uploadError: undefined, uploadErrorMessage: undefined }
            : q,
        ),
      );

      try {
        await uploader(item.file, {
          signal: ac.signal,
          onProgress: (percent) => {
            setQueue((prev) =>
              prev.map((q) =>
                q.id === itemId
                  ? { ...q, progress: percent, loaded: Math.round((q.file.size * percent) / 100) }
                  : q,
              ),
            );
          },
        });
        setQueue((prev) =>
          prev.map((q) =>
            q.id === itemId ? { ...q, status: 'success', progress: 100, loaded: q.file.size, abortController: undefined } : q,
          ),
        );
        onFileSuccess?.(item.file);
      } catch (err) {
        if (ac.signal.aborted) {
          setQueue((prev) =>
            prev.map((q) =>
              q.id === itemId ? { ...q, status: 'cancelled', abortController: undefined } : q,
            ),
          );
          return;
        }
        const apiErr = err as ApiClientError | Error;
        const code =
          apiErr instanceof ApiClientError
            ? apiErr.code
            : ((err as any)?.response?.data?.error?.code as string | undefined);
        const msg = errorMessage(code);
        setQueue((prev) =>
          prev.map((q) =>
            q.id === itemId
              ? {
                  ...q,
                  status: 'failed',
                  abortController: undefined,
                  uploadError: code,
                  uploadErrorMessage: msg,
                }
              : q,
          ),
        );
      }
    },
    [uploader, onFileSuccess, errorMessage],
  );

  // ─── 并发调度 ──────────────────────────────────────────────────────────

  useEffect(() => {
    const inFlight = queue.filter((q) => q.status === 'uploading').length;
    const slots = MAX_CONCURRENT_UPLOADS - inFlight;
    if (slots <= 0) return;
    const pending = queue.filter((q) => q.status === 'queued').slice(0, slots);
    if (pending.length === 0) return;
    pending.forEach((p) => {
      startUpload(p.id);
    });
  }, [queue, startUpload]);

  // ─── 全部完成回调 ───────────────────────────────────────────────────────

  useEffect(() => {
    if (queue.length === 0) return;
    const settled = queue.every(
      (q) => q.status === 'success' || q.status === 'failed' || q.status === 'cancelled',
    );
    if (settled) {
      onAllSettled?.(queue);
    }
    // 只在状态全部 settled 的临界点触发一次
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [queue.map((q) => q.status).join(',')]);

  // ─── 操作 ──────────────────────────────────────────────────────────────

  const removeItem = useCallback((itemId: string) => {
    setQueue((prev) => {
      const item = prev.find((q) => q.id === itemId);
      if (item?.abortController) item.abortController.abort();
      return prev.filter((q) => q.id !== itemId);
    });
  }, []);

  const cancelItem = useCallback((itemId: string) => {
    setQueue((prev) =>
      prev.map((q) => {
        if (q.id !== itemId) return q;
        if (q.abortController) q.abortController.abort();
        return { ...q, status: 'cancelled', abortController: undefined };
      }),
    );
  }, []);

  const retryItem = useCallback((itemId: string) => {
    setQueue((prev) =>
      prev.map((q) =>
        q.id === itemId && (q.status === 'failed' || q.status === 'cancelled') && !q.validateError
          ? { ...q, status: 'queued', progress: 0, loaded: 0, uploadError: undefined, uploadErrorMessage: undefined }
          : q,
      ),
    );
  }, []);

  // ─── 总进度 ────────────────────────────────────────────────────────────

  const totalProgress = useMemo(() => {
    const valid = queue.filter((q) => !q.validateError);
    if (valid.length === 0) return { loaded: 0, total: 0 };
    const total = valid.reduce((sum, q) => sum + q.file.size, 0);
    const loaded = valid.reduce((sum, q) => sum + q.loaded, 0);
    return { loaded, total };
  }, [queue]);

  const hasActiveUploads = queue.some((q) => q.status === 'uploading' || q.status === 'queued');

  // ─── 渲染 ──────────────────────────────────────────────────────────────

  return (
    <div className={cn('space-y-4', className)}>
      {/* 拖拽区 */}
      <div
        onDragOver={onDragOver}
        onDragLeave={onDragLeave}
        onDrop={onDrop}
        onClick={() => fileInputRef.current?.click()}
        className={cn(
          'border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors',
          isDragOver ? 'border-blue-500 bg-blue-50' : 'border-gray-300 hover:border-gray-400',
        )}
      >
        <Upload className="mx-auto h-10 w-10 text-gray-400" />
        <div className="mt-3 text-sm font-medium text-gray-700">
          {isDragOver ? a.dropzoneActive : a.dropzoneIdle}
        </div>
        <div className="mt-1 text-xs text-gray-500">{a.dropzoneSupported}</div>
        <input
          ref={fileInputRef}
          type="file"
          multiple
          className="hidden"
          accept={Array.from(ALLOWED_ATTACHMENT_MIME).join(',')}
          onChange={onPickerChange}
        />
      </div>

      {/* 队列 */}
      {queue.length > 0 && (
        <div className="space-y-2">
          <div className="text-sm font-medium text-gray-700">{a.queueTitle}</div>
          <ul className="space-y-2 max-h-72 overflow-y-auto">
            {queue.map((q) => (
              <li
                key={q.id}
                className={cn(
                  'flex items-center gap-3 rounded border p-3',
                  q.status === 'failed' && 'border-red-200 bg-red-50',
                  q.status === 'success' && 'border-green-200 bg-green-50',
                  q.status === 'uploading' && 'border-blue-200 bg-blue-50/50',
                )}
              >
                <div className="flex-1 min-w-0">
                  <div className="text-sm font-medium truncate text-gray-900">{q.file.name}</div>
                  <div className="text-xs text-gray-500">
                    {formatBytes(q.file.size)}
                    {q.status === 'uploading' && (
                      <span className="ml-2 text-blue-600">
                        {a.queueStatusUploading.replace('{percent}', String(q.progress))}
                      </span>
                    )}
                    {q.status === 'success' && (
                      <span className="ml-2 text-green-600">{a.queueStatusSuccess}</span>
                    )}
                    {q.status === 'failed' && (
                      <span className="ml-2 text-red-600">{q.uploadErrorMessage || a.queueStatusFailed}</span>
                    )}
                    {q.status === 'cancelled' && (
                      <span className="ml-2 text-gray-600">{a.queueStatusCancelled}</span>
                    )}
                    {q.status === 'queued' && (
                      <span className="ml-2 text-gray-600">{a.queueStatusQueued}</span>
                    )}
                  </div>
                  {q.status === 'uploading' && (
                    <div className="mt-1 h-1 w-full overflow-hidden rounded bg-gray-200">
                      <div
                        className="h-1 bg-blue-500 transition-all"
                        style={{ width: `${q.progress}%` }}
                      />
                    </div>
                  )}
                </div>

                {/* 操作按钮 */}
                <div className="flex items-center gap-1">
                  {q.status === 'uploading' && (
                    <Button type="button" variant="ghost" size="sm" onClick={() => cancelItem(q.id)}>
                      <Loader2 className="h-3 w-3 mr-1 animate-spin" />
                      {a.queueCancel}
                    </Button>
                  )}
                  {(q.status === 'failed' || q.status === 'cancelled') && !q.validateError && (
                    <Button type="button" variant="ghost" size="sm" onClick={() => retryItem(q.id)}>
                      <RefreshCw className="h-3 w-3 mr-1" />
                      {a.queueRetry}
                    </Button>
                  )}
                  <Button type="button" variant="ghost" size="sm" onClick={() => removeItem(q.id)}>
                    <X className="h-3 w-3" />
                  </Button>
                </div>
              </li>
            ))}
          </ul>

          {/* 总进度 */}
          {totalProgress.total > 0 && (
            <div className="text-xs text-gray-500">
              {a.queueTotalProgress
                .replace('{loaded}', formatBytes(totalProgress.loaded))
                .replace('{total}', formatBytes(totalProgress.total))}
            </div>
          )}
        </div>
      )}

      {/* 关闭按钮 */}
      {onRequestClose && (
        <div className="flex justify-end pt-2 border-t">
          <Button
            type="button"
            variant="outline"
            onClick={() => {
              if (hasActiveUploads) {
                if (!window.confirm(a.confirmCloseWhileUploading)) return;
                // 中断所有进行中的上传
                queueRef.current.forEach((q) => q.abortController?.abort());
              }
              onRequestClose();
            }}
          >
            {a.closeButton}
          </Button>
        </div>
      )}
    </div>
  );
}

function formatBytes(n: number): string {
  if (n < 1024) return `${n} B`;
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
  if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`;
  return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
}

export { MAX_ATTACHMENT_SIZE, MAX_FILES_PER_UPLOAD };
