import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
import DownloadOutlined from "@mui/icons-material/DownloadOutlined";
import FileUploadOutlined from "@mui/icons-material/FileUploadOutlined";
import ImageOutlined from "@mui/icons-material/ImageOutlined";
import InsertDriveFileOutlined from "@mui/icons-material/InsertDriveFileOutlined";
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined";
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
import CircularProgress from "@mui/material/CircularProgress";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogTitle from "@mui/material/DialogTitle";
import Tooltip from "@mui/material/Tooltip";
import { useEffect, useId, useRef, useState } from "react";
import { downloadResponse } from "@/shared/api/download";
import { uploadFile, uploadImage } from "@/shared/api/upload";
import { Button } from "@/shared/ui/Button.jsx";
import { Mp4Preview } from "@/shared/ui/Mp4Preview.jsx";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import styles from "./UploadField.module.css";

const pagWasmURL = "/vendor/libpag.wasm";
let pagModulePromise;

export function UploadField({
    accept,
    className,
    disabled = false,
    density = "regular",
    kind = "image",
    label,
    hideType = false,
    mp4Layout,
    onChange,
    onFileSelected,
    onUploadingChange,
    panelClassName,
    previewClassName,
    showSourceActions = false,
    uploadKind,
    value = "",
}) {
    const inputId = useId();
    const inputRef = useRef(null);
    const uploadGenerationRef = useRef(0);
    const onUploadingChangeRef = useRef(onUploadingChange);
    const { showToast } = useToast();
    const [uploading, setUploading] = useState(false);
    const [localPreview, setLocalPreview] = useState(null);
    const [downloading, setDownloading] = useState(false);
    const [videoPreviewOpen, setVideoPreviewOpen] = useState(false);
    const isImage = kind === "image";
    const source = localPreview?.url || value;
    const assetKind = localPreview?.assetKind || getAssetKind(value, kind);
    const displayName = localPreview?.name || getDisplayValue(value);
    const inputAccept = accept || undefined;
    const canPreviewVideo = Boolean(source && assetKind === "mp4");
    const localPreviewURL = localPreview?.url;
    const previewMp4Layout = Object.prototype.hasOwnProperty.call(localPreview?.selectionResult || {}, "mp4Layout")
        ? localPreview.selectionResult.mp4Layout
        : mp4Layout;

    useEffect(() => {
        onUploadingChangeRef.current = onUploadingChange;
    }, [onUploadingChange]);

    useEffect(() => {
        return () => {
            // 上传请求无法中途取消时，用代次作废迟到结果，避免弹窗关闭后回写下一次编辑表单。
            uploadGenerationRef.current += 1;
            onUploadingChangeRef.current?.(false);
        };
    }, []);

    useEffect(() => {
        return () => {
            if (localPreviewURL) {
                URL.revokeObjectURL(localPreviewURL);
            }
        };
    }, [localPreviewURL]);

    const openPicker = () => {
        if (!disabled && !uploading) {
            inputRef.current?.click();
        }
    };

    const handleFileChange = async (event) => {
        const file = event.target.files?.[0];
        event.target.value = "";
        if (!file) {
            return;
        }

        setLocalPreview((previous) => {
            if (previous?.url) {
                URL.revokeObjectURL(previous.url);
            }
            return {
                assetKind: getAssetKind(file.name, kind, file.type),
                name: file.name,
                url: URL.createObjectURL(file),
            };
        });
        const uploadGeneration = ++uploadGenerationRef.current;
        setUploading(true);
        onUploadingChange?.(true);
        try {
            const selectionResult = await onFileSelected?.(file);
            if (uploadGeneration !== uploadGenerationRef.current) {
                return;
            }
            setLocalPreview((previous) => (previous ? { ...previous, selectionResult } : previous));
            // 图片接口会校验固定的位图格式；资源素材不能被这层校验变成格式白名单，
            // 因此仅 JPEG/PNG/WebP 走图片接口，其余 PAG/SVGA/GIF/AVIF 或未知格式走通用文件接口。
            const result = shouldUploadAsImage(file, kind, uploadKind) ? await uploadImage(file) : await uploadFile(file);
            if (uploadGeneration !== uploadGenerationRef.current) {
                return;
            }
            // 只有媒体检测和上传都成功后才把检测结果交给表单，避免新布局残留在旧 URL 上。
            if (selectionResult === undefined) {
                onChange(result.url);
            } else {
                onChange(result.url, selectionResult);
            }
            setLocalPreview(null);
            showToast("上传成功", "success");
        } catch (err) {
            if (uploadGeneration !== uploadGenerationRef.current) {
                return;
            }
            setLocalPreview(null);
            showToast(err.message || "上传失败", "error");
        } finally {
            if (uploadGeneration === uploadGenerationRef.current) {
                setUploading(false);
                onUploadingChange?.(false);
            }
        }
    };

    const clearValue = () => {
        setLocalPreview(null);
        setVideoPreviewOpen(false);
        onChange("");
    };

    const openSource = () => {
        if (source) {
            window.open(source, "_blank", "noopener,noreferrer");
        }
    };

    const downloadSource = async () => {
        if (!source || downloading) {
            return;
        }
        setDownloading(true);
        try {
            // 先拉取为同源 Blob 再触发下载，避免跨域素材 URL 被浏览器忽略 download 属性并直接打开。
            const response = await fetch(source);
            if (!response.ok) {
                throw new Error(`素材下载失败 (${response.status})`);
            }
            await downloadResponse(response, displayName || "asset");
        } catch (err) {
            showToast(err.message || "素材下载失败", "error");
        } finally {
            setDownloading(false);
        }
    };

    return (
        <div
            className={[
                styles.root,
                density === "compact" ? styles.compact : "",
                disabled ? styles.disabled : "",
                className || "",
            ]
                .filter(Boolean)
                .join(" ")}
        >
            <div className={styles.header}>
                <span className={styles.label}>{label}</span>
                {hideType ? null : <span className={styles.type}>{assetKindLabel(assetKind, isImage)}</span>}
            </div>
            <div className={[styles.panel, panelClassName || ""].filter(Boolean).join(" ")}>
                <div className={styles.previewArea}>
                    <button
                        aria-label={label}
                        className={[styles.preview, previewClassName || ""].filter(Boolean).join(" ")}
                        disabled={disabled || uploading}
                        type="button"
                        onClick={openPicker}
                    >
                        <AssetPreview
                            assetKind={assetKind}
                            isImage={isImage}
                            mp4Layout={previewMp4Layout}
                            src={source}
                        />
                        {uploading ? (
                            <span className={styles.overlay}>
                                <CircularProgress color="inherit" size={18} />
                            </span>
                        ) : null}
                    </button>
                    {source && showSourceActions ? (
                        <span className={styles.previewActions}>
                            <Tooltip arrow title="打开">
                                <button
                                    aria-label={`打开${label}`}
                                    className={styles.previewAction}
                                    type="button"
                                    onClick={openSource}
                                >
                                    <OpenInNewOutlined fontSize="small" />
                                </button>
                            </Tooltip>
                            <Tooltip arrow title="下载">
                                <span>
                                    <button
                                        aria-label={`下载${label}`}
                                        className={styles.previewAction}
                                        disabled={downloading}
                                        type="button"
                                        onClick={downloadSource}
                                    >
                                        {downloading ? (
                                            <CircularProgress color="inherit" size={16} />
                                        ) : (
                                            <DownloadOutlined fontSize="small" />
                                        )}
                                    </button>
                                </span>
                            </Tooltip>
                        </span>
                    ) : null}
                </div>
                <div className={styles.footer}>
                    <span className={styles.name}>{displayName || "未上传"}</span>
                    <span className={styles.actions}>
                        <Tooltip arrow title={source ? "替换" : "上传"}>
                            <span>
                                <button
                                    className={styles.action}
                                    disabled={disabled || uploading}
                                    type="button"
                                    onClick={openPicker}
                                >
                                    {uploading ? (
                                        <CircularProgress color="inherit" size={16} />
                                    ) : (
                                        <FileUploadOutlined fontSize="small" />
                                    )}
                                    {source ? "替换" : "上传"}
                                </button>
                            </span>
                        </Tooltip>
                        {canPreviewVideo ? (
                            <Tooltip arrow title="查看 MP4">
                                <span>
                                    <button
                                        className={styles.action}
                                        disabled={disabled || uploading}
                                        type="button"
                                        onClick={() => setVideoPreviewOpen(true)}
                                    >
                                        <VisibilityOutlined fontSize="small" />
                                        查看
                                    </button>
                                </span>
                            </Tooltip>
                        ) : null}
                        {source ? (
                            <Tooltip arrow title="清除">
                                <span>
                                    <button
                                        className={styles.action}
                                        disabled={disabled || uploading}
                                        type="button"
                                        onClick={clearValue}
                                    >
                                        <DeleteOutlineOutlined fontSize="small" />
                                        清除
                                    </button>
                                </span>
                            </Tooltip>
                        ) : null}
                    </span>
                </div>
            </div>
            <VideoPreviewDialog
                label={label}
                mp4Layout={previewMp4Layout}
                open={videoPreviewOpen}
                src={canPreviewVideo ? source : ""}
                onClose={() => setVideoPreviewOpen(false)}
            />
            <input
                ref={inputRef}
                accept={inputAccept}
                className={styles.input}
                id={inputId}
                type="file"
                onChange={handleFileChange}
            />
        </div>
    );
}

function VideoPreviewDialog({ label, mp4Layout, onClose, open, src }) {
    return (
        <Dialog fullWidth maxWidth="sm" open={open} onClose={onClose}>
            <DialogTitle>{label} MP4</DialogTitle>
            <DialogContent>
                <div className={styles.videoPreviewFrame}>
                    {src ? <Mp4Preview autoPlay controls expanded layout={mp4Layout} src={src} /> : null}
                </div>
            </DialogContent>
            <DialogActions>
                <Button onClick={onClose}>关闭</Button>
            </DialogActions>
        </Dialog>
    );
}

function AssetPreview({ assetKind, isImage, mp4Layout, src }) {
    if (!src) {
        return (
            <span className={styles.empty}>
                {isImage ? <ImageOutlined fontSize="small" /> : <InsertDriveFileOutlined fontSize="small" />}
            </span>
        );
    }
    if (assetKind === "svga") {
        return <SVGAAssetPreview src={src} />;
    }
    if (assetKind === "pag") {
        return <PAGAssetPreview src={src} />;
    }
    if (assetKind === "mp4") {
        return <Mp4Preview autoPlay layout={mp4Layout} src={src} />;
    }
    if (assetKind === "image" || isImage) {
        return <RasterAssetPreview src={src} />;
    }
    return (
        <span className={styles.empty}>
            <InsertDriveFileOutlined fontSize="small" />
        </span>
    );
}

function RasterAssetPreview({ src }) {
    const [failed, setFailed] = useState(false);

    useEffect(() => {
        setFailed(false);
    }, [src]);

    if (failed) {
        return (
            <span className={styles.empty}>
                <ImageOutlined fontSize="small" />
            </span>
        );
    }

    return <img alt="" className={styles.image} src={src} onError={() => setFailed(true)} />;
}

function SVGAAssetPreview({ src }) {
    const containerRef = useRef(null);
    const [failed, setFailed] = useState(false);

    useEffect(() => {
        let cancelled = false;
        const container = containerRef.current;
        let player;
        setFailed(false);

        async function load() {
            try {
                const module = await import("svgaplayerweb");
                const SVGA = module.default || module;
                if (cancelled || !container) {
                    return;
                }
                container.innerHTML = "";
                player = new SVGA.Player(container);
                player.loops = 0;
                player.clearsAfterStop = false;
                player.setContentMode("AspectFit");
                const parser = new SVGA.Parser(container);
                parser.load(
                    src,
                    (videoItem) => {
                        if (cancelled) {
                            return;
                        }
                        player.setVideoItem(videoItem);
                        player.startAnimation();
                    },
                    () => {
                        if (!cancelled) {
                            setFailed(true);
                        }
                    },
                );
            } catch {
                if (!cancelled) {
                    setFailed(true);
                }
            }
        }

        load();

        return () => {
            cancelled = true;
            if (player) {
                try {
                    player.stopAnimation(true);
                    player.clear();
                } catch {
                    // Ignore player cleanup errors from partially loaded assets.
                }
            }
            if (container) {
                container.innerHTML = "";
            }
        };
    }, [src]);

    return (
        <span className={styles.player}>
            {/* svgaplayerweb 2.3.2 只会为 HTMLDivElement 创建内部 Canvas，span 会在 setVideoItem 时稳定失败。 */}
            <div ref={containerRef} className={styles.playerSurface} />
            {failed ? <span className={styles.empty}>SVGA 预览失败</span> : null}
        </span>
    );
}

function PAGAssetPreview({ src }) {
    const canvasRef = useRef(null);
    const [failed, setFailed] = useState(false);

    useEffect(() => {
        let cancelled = false;
        let pagFile;
        let pagView;
        setFailed(false);

        async function load() {
            try {
                const PAG = await getPAGModule();
                const buffer = await fetch(src).then((response) => response.arrayBuffer());
                pagFile = await PAG.PAGFile.load(buffer);
                if (cancelled || !canvasRef.current) {
                    return;
                }
                canvasRef.current.width = pagFile.width();
                canvasRef.current.height = pagFile.height();
                pagView = await PAG.PAGView.init(pagFile, canvasRef.current, { useScale: true });
                if (!pagView) {
                    throw new Error("PAGView init failed");
                }
                pagView.setRepeatCount(0);
                await pagView.play();
            } catch {
                if (!cancelled) {
                    setFailed(true);
                }
            }
        }

        load();

        return () => {
            cancelled = true;
            if (pagView) {
                pagView.destroy();
            }
            if (pagFile?.destroy) {
                pagFile.destroy();
            }
        };
    }, [src]);

    return (
        <span className={styles.player}>
            <canvas ref={canvasRef} className={styles.pagCanvas} />
            {failed ? <span className={styles.empty}>PAG 预览失败</span> : null}
        </span>
    );
}

async function getPAGModule() {
    if (!pagModulePromise) {
        pagModulePromise = import("libpag").then(({ PAGInit }) => PAGInit({ locateFile: () => pagWasmURL }));
    }
    return pagModulePromise;
}

function getDisplayValue(value) {
    if (!value) {
        return "";
    }
    const raw = String(value);
    try {
        const url = new URL(raw);
        const name = url.pathname.split("/").filter(Boolean).pop();
        return name ? decodeURIComponent(name) : raw;
    } catch {
        return raw;
    }
}

function getAssetKind(value, kind, contentType = "") {
    const extension = getExtension(value);
    if (extension === "svga") {
        return "svga";
    }
    if (extension === "pag") {
        return "pag";
    }
    if (extension === "mp4") {
        return "mp4";
    }
    if (isRasterImageExtension(extension)) {
        return "image";
    }
    const normalizedType = String(contentType || "").toLowerCase();
    if (normalizedType.includes("svga")) {
        return "svga";
    }
    if (normalizedType.includes("pag")) {
        return "pag";
    }
    if (normalizedType === "video/mp4") {
        return "mp4";
    }
    if (isRasterImageContentType(normalizedType)) {
        return "image";
    }
    return kind === "image" ? "image" : "file";
}

function shouldUploadAsImage(file, kind, uploadKind) {
    if (uploadKind !== undefined) {
        return uploadKind === "image";
    }
    if (kind !== "image") {
        return false;
    }
    const extension = getExtension(file?.name);
    if (extension) {
        return ["jpg", "jpeg", "png", "webp"].includes(extension);
    }
    return ["image/jpeg", "image/png", "image/webp"].includes(String(file?.type || "").toLowerCase());
}

function isRasterImageExtension(extension) {
    return ["avif", "gif", "jpg", "jpeg", "png", "webp"].includes(extension);
}

function isRasterImageContentType(contentType) {
    return ["image/avif", "image/gif", "image/jpeg", "image/png", "image/webp"].includes(contentType);
}

function getExtension(value) {
    if (!value) {
        return "";
    }
    const raw = String(value);
    const pathname = safePathname(raw);
    const name = pathname.split("/").filter(Boolean).pop() || raw;
    const cleanName = name.split("?")[0].split("#")[0];
    const dotIndex = cleanName.lastIndexOf(".");
    return dotIndex >= 0 ? cleanName.slice(dotIndex + 1).toLowerCase() : "";
}

function safePathname(value) {
    try {
        return new URL(value).pathname;
    } catch {
        return value;
    }
}

function assetKindLabel(assetKind, isImage) {
    if (assetKind === "image") {
        return "图片";
    }
    if (assetKind === "svga") {
        return "SVGA";
    }
    if (assetKind === "pag") {
        return "PAG";
    }
    if (assetKind === "mp4") {
        return "MP4";
    }
    return isImage ? "图片" : "文件";
}
