"use client";

import { useCallback, useEffect, useMemo, useRef, useState, type DragEvent, type ChangeEvent } from "react";
import {
  AudioOutlined,
  CheckOutlined,
  CloseOutlined,
  FileImageOutlined,
  PlusOutlined,
  PlayCircleOutlined,
} from "@ant-design/icons";
import { Button, Empty, Modal, TreeSelect, message } from "antd";

import { useI18n } from "@/i18n";
import { authFetch } from "@/utils/authFetch";
import { uploadFileToGatewayMediaWithProgress, type GatewayMediaKind } from "@/utils/gatewayMediaUpload";
import { ROOT_PARENT_ID, type UserFileItem, type UserFileMutationResponse } from "@/utils/userFileLibrary";

export interface AssetLibraryUploadFolderOption {
  id: number;
  label: string;
}

interface UploadFolderTreeNode {
  title: React.ReactNode;
  value: number;
  key: number;
  isLeaf: boolean;
  children?: UploadFolderTreeNode[];
}

interface AssetLibraryUploadModalProps {
  open: boolean;
  foldersByParentId: Record<number, UserFileItem[]>;
  loadedFolderIds: Set<number>;
  loadingFolderIds: Set<number>;
  initialFolderId: number;
  onLoadFolderChildren: (folderId: number) => void | Promise<void>;
  onClose: () => void;
  onUploaded: (folderId: number) => void | Promise<void>;
  onError?: (message: string) => void;
}

const uploadAccept = "image/*,video/*,audio/*";

type UploadItemStatus = "uploading" | "done" | "error";

interface UploadAssetItem {
  id: string;
  key: string;
  file: File;
  name: string;
  kind: GatewayMediaKind;
  status: UploadItemStatus;
  progress: number;
  url?: string;
  previewUrl?: string;
  error?: string;
}

function AssetLibraryUploadModalHeader({
  title,
  onClose,
}: {
  title: string;
  onClose: () => void;
}) {
  const { t } = useI18n();

  return (
    <div className="flex h-12 shrink-0 items-center justify-between px-4">
      <h2 className="text-sm font-semibold text-[var(--text-primary)]">{title}</h2>
      <Button
        type="text"
        aria-label={t("common.close")}
        icon={<CloseOutlined className="text-[14px]" />}
        onClick={onClose}
        className="!flex !h-8 !w-8 !items-center !justify-center !rounded-lg !p-0 !text-[var(--text-muted)] hover:!bg-[var(--surface-hover)] hover:!text-[var(--text-primary)]"
      />
    </div>
  );
}

function getUploadMediaKind(file: File): GatewayMediaKind | null {
  if (file.type.startsWith("image/")) return "image";
  if (file.type.startsWith("video/")) return "video";
  if (file.type.startsWith("audio/")) return "audio";
  return null;
}

function hasDraggedFiles(event: DragEvent<HTMLDivElement>) {
  return Array.from(event.dataTransfer.types).includes("Files");
}

function UploadAssetCard({
  item,
  onRemove,
}: {
  item: UploadAssetItem;
  onRemove: (item: UploadAssetItem) => void;
}) {
  const { t } = useI18n();
  const iconClassName = "text-[28px] text-[var(--text-muted)]";
  const icon = item.kind === "image"
    ? <FileImageOutlined className={iconClassName} />
    : item.kind === "video"
      ? <PlayCircleOutlined className={iconClassName} />
      : <AudioOutlined className={iconClassName} />;
  const previewUrl = item.previewUrl || item.url;

  return (
    <div className="group/card min-w-0" onClick={(event) => event.stopPropagation()}>
      <div className="relative flex h-[120px] w-[120px] flex-col items-center justify-center rounded-lg border border-[var(--border-subtle)] bg-[var(--surface-2)]">
        <Button
          type="text"
          aria-label={t("assetLibrary.delete")}
          icon={<CloseOutlined className="text-[12px]" />}
          onClick={(event) => {
            event.stopPropagation();
            onRemove(item);
          }}
          className="!absolute !top-1 !right-1 !z-10 !flex !h-6 !w-6 !items-center !justify-center !rounded-md !bg-black/60 !p-0 !text-white opacity-0 transition-opacity group-hover/card:opacity-100 hover:!bg-black/80 hover:!text-white"
        />
        {item.status === "uploading" ? (
          <>
            <span className="text-xs font-medium text-[var(--text-muted)]">{t("assetLibrary.uploading")}</span>
            <div className="mt-3 h-1 w-16 overflow-hidden rounded-full bg-[var(--surface-3)]">
              <span className="block h-full rounded-full bg-[var(--text-muted)]" style={{ width: `${item.progress}%` }} />
            </div>
          </>
        ) : item.status === "error" ? (
          <span className="px-2 text-center text-xs font-medium text-red-300">{item.error || t("assetLibrary.uploadFailed")}</span>
        ) : previewUrl && item.kind === "image" ? (
          <img src={previewUrl} alt="" className="h-full w-full rounded-lg object-cover" />
        ) : (
          icon
        )}
      </div>
      <div className="mt-2 w-[120px] truncate text-xs font-medium text-[var(--text-primary)]">{item.name}</div>
    </div>
  );
}

export function AssetLibraryUploadModal({
  open,
  foldersByParentId,
  loadedFolderIds,
  loadingFolderIds,
  initialFolderId,
  onLoadFolderChildren,
  onClose,
  onUploaded,
  onError,
}: AssetLibraryUploadModalProps) {
  const { t } = useI18n();
  const [selectedFolderId, setSelectedFolderId] = useState(initialFolderId);
  const [uploadItems, setUploadItems] = useState<UploadAssetItem[]>([]);
  const [submitting, setSubmitting] = useState(false);
  const [dragActive, setDragActive] = useState(false);
  const [expandedFolderIds, setExpandedFolderIds] = useState<(string | number)[]>([]);
  const uploadSequenceRef = useRef(0);
  const uploadKeySetRef = useRef<Set<string>>(new Set());
  const dragDepthRef = useRef(0);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const rootFolders = foldersByParentId[ROOT_PARENT_ID] || [];

  const treeData = useMemo(() => {
    const buildNodes = (parentId: number): UploadFolderTreeNode[] => {
      const folders = foldersByParentId[parentId] || [];
      return folders.map((folder) => {
        const loaded = loadedFolderIds.has(folder.id);
        const children = foldersByParentId[folder.id] || [];
        return {
          title: (
            <span className="flex min-w-0 flex-1 items-center justify-between gap-3">
              <span className="min-w-0 truncate">{folder.name}</span>
              {selectedFolderId === folder.id ? <CheckOutlined className="shrink-0 text-[13px]" /> : null}
            </span>
          ),
          value: folder.id,
          key: folder.id,
          isLeaf: loaded && children.length === 0,
          children: loaded && children.length > 0 ? buildNodes(folder.id) : undefined,
        };
      });
    };
    return buildNodes(ROOT_PARENT_ID);
  }, [foldersByParentId, loadedFolderIds, selectedFolderId]);

  const folderIdExists = useCallback((folderId: number) => {
    if (folderId === ROOT_PARENT_ID) return false;
    return Object.values(foldersByParentId).some((folders) => folders.some((folder) => folder.id === folderId));
  }, [foldersByParentId]);

  useEffect(() => {
    if (!open) return;
    void onLoadFolderChildren(ROOT_PARENT_ID);
    setSelectedFolderId(initialFolderId !== ROOT_PARENT_ID ? initialFolderId : 0);
    setUploadItems([]);
    setDragActive(false);
    uploadKeySetRef.current.clear();
    dragDepthRef.current = 0;
  }, [initialFolderId, onLoadFolderChildren, open]);

  useEffect(() => {
    if (!open || selectedFolderId !== 0 || initialFolderId !== ROOT_PARENT_ID || rootFolders.length === 0) return;
    setSelectedFolderId(rootFolders[0].id);
  }, [initialFolderId, open, rootFolders, selectedFolderId]);

  const startUpload = useCallback((file: File) => {
    const kind = getUploadMediaKind(file);
    if (!kind) {
      void message.error(t("assetLibrary.selectMediaFile"));
      return;
    }

    const id = `${Date.now()}-${uploadSequenceRef.current++}`;
    const item: UploadAssetItem = {
      id,
      key: `${file.name}-${file.size}-${file.lastModified}`,
      file,
      name: file.name || `${t("assetLibrary.asset")} ${uploadSequenceRef.current}`,
      kind,
      status: "uploading",
      progress: 0,
    };
    setUploadItems((current) => [...current, item]);

    void uploadFileToGatewayMediaWithProgress(file, kind, (progress) => {
      setUploadItems((current) => current.map((currentItem) => (
        currentItem.id === id ? { ...currentItem, progress } : currentItem
      )));
    })
      .then((uploaded) => {
        setUploadItems((current) => current.map((currentItem) => (
          currentItem.id === id
            ? {
                ...currentItem,
                status: "done",
                progress: 100,
                url: uploaded.url,
                previewUrl: uploaded.previewUrl,
              }
            : currentItem
        )));
      })
      .catch((err: unknown) => {
        const messageText = err instanceof Error ? err.message : t("assetLibrary.uploadFailed");
        setUploadItems((current) => current.map((currentItem) => (
          currentItem.id === id
            ? { ...currentItem, status: "error", error: messageText, progress: 0 }
            : currentItem
        )));
      });
  }, [t]);

  const handleFiles = useCallback((files: FileList | File[]) => {
    Array.from(files).forEach((file) => {
      const key = `${file.name}-${file.size}-${file.lastModified}`;
      if (uploadKeySetRef.current.has(key)) return;
      uploadKeySetRef.current.add(key);
      startUpload(file);
    });
  }, [startUpload]);

  const handleRemoveUploadItem = useCallback((item: UploadAssetItem) => {
    uploadKeySetRef.current.delete(item.key);
    setUploadItems((current) => current.filter((currentItem) => currentItem.id !== item.id));
  }, []);

  const handleFileInputChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    const files = event.target.files;
    if (files) handleFiles(files);
    event.target.value = "";
  }, [handleFiles]);

  const handleDragEnter = useCallback((event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    if (!hasDraggedFiles(event)) return;
    dragDepthRef.current += 1;
    setDragActive(true);
  }, []);

  const handleDragOver = useCallback((event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    if (!hasDraggedFiles(event)) return;
    event.dataTransfer.dropEffect = "copy";
  }, []);

  const handleDragLeave = useCallback((event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    dragDepthRef.current = Math.max(0, dragDepthRef.current - 1);
    if (dragDepthRef.current === 0) setDragActive(false);
  }, []);

  const handleDrop = useCallback((event: DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    dragDepthRef.current = 0;
    setDragActive(false);
    if (event.dataTransfer.files.length > 0) {
      handleFiles(event.dataTransfer.files);
    }
  }, [handleFiles]);

  const handleConfirm = useCallback(async () => {
    if (submitting) return;
    const uploadedUrls = uploadItems
      .filter((item): item is UploadAssetItem & { url: string } => item.status === "done" && Boolean(item.url))
      .map((item) => item.url);
    if (uploadedUrls.length === 0) return;

    setSubmitting(true);
    try {
      const response = await authFetch("/api/popi/user-file/upload", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          parentId: selectedFolderId,
          urlList: uploadedUrls,
        }),
      });
      const payload = (await response.json().catch(() => null)) as UserFileMutationResponse | null;
      if (!response.ok || !payload?.success) {
        throw new Error(payload?.error || t("assetLibrary.uploadFailed"));
      }

      await onUploaded(selectedFolderId);
      void message.success(t("assetLibrary.uploadSuccess"));
      onClose();
    } catch (err) {
      const messageText = err instanceof Error ? err.message : t("assetLibrary.uploadFailed");
      onError?.(messageText);
      void message.error(messageText);
    } finally {
      setSubmitting(false);
    }
  }, [onClose, onError, onUploaded, selectedFolderId, t, submitting, uploadItems]);

  const hasUploading = uploadItems.some((item) => item.status === "uploading");
  const uploadedCount = uploadItems.filter((item) => item.status === "done" && item.url).length;
  const canConfirm = uploadedCount > 0
    && !hasUploading
    && !submitting
    && folderIdExists(selectedFolderId);

  const handleTreeExpand = useCallback((expandedKeys: (string | number)[]) => {
    setExpandedFolderIds(expandedKeys);
    expandedKeys.forEach((key) => {
      const folderId = Number(key);
      if (Number.isInteger(folderId) && folderId > 0) {
        void onLoadFolderChildren(folderId);
      }
    });
  }, [onLoadFolderChildren]);

  return (
    <Modal
      open={open}
      title={null}
      closable={false}
      centered
      width={640}
      footer={null}
      rootClassName="[&_.ant-modal-container]:!p-0"
      onCancel={submitting ? undefined : onClose}
      className={[
        "nodrag nopan nowheel",
        "[&_.ant-modal-content]:!overflow-hidden [&_.ant-modal-content]:!rounded-lg [&_.ant-modal-content]:!border-0 [&_.ant-modal-content]:!bg-[var(--surface-1)] [&_.ant-modal-content]:!p-0 [&_.ant-modal-content]:!shadow-[var(--shadow-panel)]",
        "[&_.ant-modal-body]:!p-0",
      ].join(" ")}
    >
      <div className="text-[var(--text-primary)]">
        <AssetLibraryUploadModalHeader
          title={t("assetLibrary.uploadAsset")}
          onClose={() => {
            if (!submitting) onClose();
          }}
        />
        <div className="px-4 pb-4">
          <div className="mb-4">
            <div className="mb-2 text-xs text-[var(--text-muted)]">{t("assetLibrary.uploadLocation")}</div>
            {rootFolders.length === 0 && loadedFolderIds.has(ROOT_PARENT_ID) ? (
              <div className="rounded-lg border border-[var(--border-subtle)] bg-[var(--surface-2)] py-4">
                <Empty
                  image={Empty.PRESENTED_IMAGE_SIMPLE}
                  description={<span className="text-xs text-[var(--text-disabled)]">{t("assetLibrary.noFolders")}</span>}
                />
              </div>
            ) : (
              <TreeSelect
                value={folderIdExists(selectedFolderId) ? selectedFolderId : undefined}
                treeData={treeData}
                loadData={(node) => Promise.resolve(onLoadFolderChildren(Number(node.value)))}
                loading={loadingFolderIds.size > 0}
                disabled={submitting}
                onChange={setSelectedFolderId}
                placeholder={t("assetLibrary.selectFolder")}
                treeDefaultExpandAll={false}
                treeExpandedKeys={expandedFolderIds}
                onTreeExpand={handleTreeExpand}
                className="w-full [&_.ant-select-selector]:!border-[var(--border-subtle)] [&_.ant-select-selector]:!bg-[var(--surface-2)] [&_.ant-select-selection-item]:!text-[var(--text-primary)] [&_.ant-select-selection-placeholder]:!text-[var(--text-muted)]"
                popupClassName="nodrag nopan nowheel"
              />
            )}
          </div>

          <input
            ref={fileInputRef}
            type="file"
            multiple
            accept={uploadAccept}
            className="hidden"
            onChange={handleFileInputChange}
          />
          <div
            onDragEnter={handleDragEnter}
            onDragOver={handleDragOver}
            onDragLeave={handleDragLeave}
            onDrop={handleDrop}
            className={[
              "h-[360px] overflow-y-auto rounded-lg border border-dashed bg-[var(--surface-2)] p-3 text-left [scrollbar-color:var(--text-muted)_transparent] [scrollbar-width:thin]",
              dragActive ? "border-white/80" : "border-[var(--border-subtle)]",
              "transition-colors",
              submitting ? "cursor-wait opacity-80" : "",
            ].join(" ")}
          >
            <div className="grid grid-cols-4 content-start items-start gap-3">
              <button
                type="button"
                disabled={submitting}
                onClick={(event) => {
                  event.stopPropagation();
                  fileInputRef.current?.click();
                }}
                className="flex h-[120px] w-[120px] items-center justify-center rounded-lg border border-dashed border-[var(--border-subtle)] bg-[var(--surface-2)] text-[var(--text-muted)] transition-colors hover:border-[var(--text-muted)] hover:text-[var(--text-primary)] disabled:cursor-wait disabled:opacity-60"
              >
                <PlusOutlined className="text-[28px]" />
              </button>
              {uploadItems.map((item) => (
                <UploadAssetCard key={item.id} item={item} onRemove={handleRemoveUploadItem} />
              ))}
            </div>
          </div>
          <div className="mt-3 text-xs text-[var(--text-muted)]">
            {t("assetLibrary.uploadDropHint")}
          </div>
        </div>
        <div className="flex justify-end gap-2 px-4 pb-4">
          <Button
            disabled={submitting}
            onClick={onClose}
            className="!h-8 !rounded-lg !border-0 !bg-[var(--surface-3)] !px-4 !text-sm !font-medium !text-[var(--text-secondary)] hover:!bg-[var(--surface-hover)] hover:!text-[var(--text-primary)]"
          >
            {t("common.cancel")}
          </Button>
          <Button
            type="primary"
            loading={submitting}
            disabled={!canConfirm}
            onClick={() => void handleConfirm()}
            className="!h-8 !rounded-lg !border-0 !bg-[#3b82f6] !px-4 !text-sm !font-medium !text-white hover:!bg-[#60a5fa]"
          >
            {t("common.confirm")}
          </Button>
        </div>
      </div>
    </Modal>
  );
}
