import PauseRounded from "@mui/icons-material/PauseRounded";
import PlayArrowRounded from "@mui/icons-material/PlayArrowRounded";
import { useEffect, useRef, useState } from "react";
import styles from "./Mp4Preview.module.css";

const alphaLowThreshold = 0.08;
const alphaHighThreshold = 0.3;

export function Mp4Preview({ autoPlay = false, controls = false, expanded = false, layout, src }) {
    const layoutKey = transparentLayoutKey(layout);
    const reducedMotion = prefersReducedMotion();

    if (!layoutKey) {
        return (
            <NativeMp4Preview autoPlay={autoPlay && !reducedMotion} controls={controls} expanded={expanded} src={src} />
        );
    }

    return (
        <AlphaMp4Preview
            autoPlay={autoPlay && !reducedMotion}
            controls={controls}
            expanded={expanded}
            layoutKey={layoutKey}
            src={src}
        />
    );
}

function NativeMp4Preview({ autoPlay, controls, expanded, src }) {
    const [failed, setFailed] = useState(false);

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

    return (
        <span className={[styles.root, expanded ? styles.expanded : ""].filter(Boolean).join(" ")}>
            {failed ? (
                <span className={styles.error}>MP4 预览失败</span>
            ) : (
                <video
                    autoPlay={autoPlay}
                    className={styles.media}
                    controls={controls}
                    loop={autoPlay}
                    muted={autoPlay}
                    playsInline
                    preload={autoPlay ? "auto" : "metadata"}
                    src={src}
                    onError={() => setFailed(true)}
                />
            )}
        </span>
    );
}

function AlphaMp4Preview({ autoPlay, controls, expanded, layoutKey, src }) {
    const canvasRef = useRef(null);
    const videoRef = useRef(null);
    const [failed, setFailed] = useState(false);
    const [playing, setPlaying] = useState(false);

    useEffect(() => {
        const canvas = canvasRef.current;
        const video = videoRef.current;
        if (!canvas || !video || !src) {
            return undefined;
        }

        let cancelled = false;
        let started = false;
        let renderer;
        let videoFrameId;
        let animationFrameId;
        let frameScheduled = false;
        let lastMediaTime = -1;
        let stopped = false;
        const layout = JSON.parse(layoutKey);

        setFailed(false);
        setPlaying(false);

        const fail = () => {
            if (cancelled || stopped) {
                return;
            }
            // 绘制失败后立即停止帧循环并释放 GPU，避免 RAF 回退路径永久空转。
            stopped = true;
            if (videoFrameId !== undefined && typeof video.cancelVideoFrameCallback === "function") {
                video.cancelVideoFrameCallback(videoFrameId);
            }
            if (animationFrameId !== undefined) {
                window.cancelAnimationFrame(animationFrameId);
            }
            renderer?.destroy();
            renderer = undefined;
            video.pause();
            setPlaying(false);
            setFailed(true);
        };

        const drawFrame = (mediaTime = video.currentTime) => {
            if (cancelled || stopped || !renderer || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
                return;
            }
            // RAF 会比视频帧率更快；只在媒体时间变化时上传纹理，避免无意义地重复占用 GPU。
            if (mediaTime === lastMediaTime) {
                return;
            }
            lastMediaTime = mediaTime;
            try {
                renderer.draw();
            } catch {
                fail();
            }
        };

        const scheduleFrame = () => {
            if (cancelled || stopped || frameScheduled || video.paused) {
                return;
            }
            if (typeof video.requestVideoFrameCallback === "function") {
                frameScheduled = true;
                videoFrameId = video.requestVideoFrameCallback((_, metadata) => {
                    frameScheduled = false;
                    drawFrame(metadata?.mediaTime);
                    scheduleFrame();
                });
                return;
            }
            frameScheduled = true;
            animationFrameId = window.requestAnimationFrame(() => {
                frameScheduled = false;
                drawFrame();
                scheduleFrame();
            });
        };

        const handlePlay = () => {
            if (!cancelled && !stopped) {
                setPlaying(true);
                scheduleFrame();
            }
        };
        const handlePause = () => {
            if (!cancelled) {
                setPlaying(false);
            }
        };

        const start = () => {
            if (cancelled || stopped || started || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
                return;
            }
            started = true;
            try {
                // 透明 MP4 必须按检测得到的精确矩形合成；custom/VAPC 的 RGB 与 Alpha 尺寸可能并不相等。
                renderer = createAlphaRenderer(canvas, video, layout, expanded ? 1280 : 512);
                drawFrame();
                if (autoPlay) {
                    video.play().catch(() => {
                        // 浏览器若阻止自动播放，首帧仍会保留；再次打开弹窗会由用户手势触发播放。
                    });
                }
            } catch {
                fail();
            }
        };

        video.addEventListener("loadeddata", start);
        video.addEventListener("error", fail);
        video.addEventListener("play", handlePlay);
        video.addEventListener("pause", handlePause);
        // Canvas 读取跨域视频必须先设置 anonymous；当前上传媒体域允许 CORS，本地 Blob URL 也兼容该设置。
        video.crossOrigin = "anonymous";
        video.loop = true;
        video.muted = true;
        video.playsInline = true;
        video.preload = "auto";
        video.src = src;
        video.load();
        start();

        return () => {
            cancelled = true;
            video.removeEventListener("loadeddata", start);
            video.removeEventListener("error", fail);
            video.removeEventListener("play", handlePlay);
            video.removeEventListener("pause", handlePause);
            if (videoFrameId !== undefined && typeof video.cancelVideoFrameCallback === "function") {
                video.cancelVideoFrameCallback(videoFrameId);
            }
            if (animationFrameId !== undefined) {
                window.cancelAnimationFrame(animationFrameId);
            }
            renderer?.destroy();
            video.pause();
            video.removeAttribute("src");
            video.load();
        };
    }, [autoPlay, expanded, layoutKey, src]);

    const togglePlayback = () => {
        const video = videoRef.current;
        if (!video || failed) {
            return;
        }
        if (video.paused) {
            // 用户手势仍可能被浏览器策略暂时拒绝；保留播放入口，允许用户重试。
            video.play().catch(() => {});
        } else {
            video.pause();
        }
    };

    return (
        <span className={[styles.root, expanded ? styles.expanded : ""].filter(Boolean).join(" ")}>
            <canvas ref={canvasRef} className={styles.media} />
            <video ref={videoRef} aria-hidden="true" className={styles.sourceVideo} tabIndex={-1} />
            {failed ? <span className={styles.error}>透明 MP4 预览失败</span> : null}
            {controls && !failed ? (
                <button
                    aria-label={playing ? "暂停透明 MP4" : "播放透明 MP4"}
                    className={styles.playbackControl}
                    type="button"
                    onClick={togglePlayback}
                >
                    {playing ? <PauseRounded fontSize="small" /> : <PlayArrowRounded fontSize="small" />}
                    {playing ? "暂停" : "播放"}
                </button>
            ) : null}
        </span>
    );
}

function createAlphaRenderer(canvas, video, layout, maxEdge) {
    const outputSize = scaledOutputSize(layout.rgb_frame[2], layout.rgb_frame[3], maxEdge);
    canvas.width = outputSize.width;
    canvas.height = outputSize.height;

    const gl = canvas.getContext("webgl", {
        alpha: true,
        antialias: false,
        premultipliedAlpha: false,
        preserveDrawingBuffer: false,
    });
    if (!gl) {
        return createCanvas2DRenderer(canvas, video, layout);
    }

    const program = createProgram(gl, vertexShaderSource, fragmentShaderSource);
    const positionBuffer = gl.createBuffer();
    const texture = gl.createTexture();
    if (!positionBuffer || !texture) {
        throw new Error("MP4 WebGL 初始化失败");
    }

    gl.useProgram(program);
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]), gl.STATIC_DRAW);
    const positionLocation = gl.getAttribLocation(program, "aPosition");
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.uniform1i(gl.getUniformLocation(program, "uTexture"), 0);
    gl.uniform4fv(gl.getUniformLocation(program, "uRgbRect"), normalizedRect(layout.rgb_frame, layout));
    gl.uniform4fv(gl.getUniformLocation(program, "uAlphaRect"), normalizedRect(layout.alpha_frame, layout));
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(0, 0, 0, 0);

    return {
        destroy() {
            gl.deleteTexture(texture);
            gl.deleteBuffer(positionBuffer);
            gl.deleteProgram(program);
        },
        draw() {
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
            gl.drawArrays(gl.TRIANGLES, 0, 6);
        },
    };
}

function createCanvas2DRenderer(canvas, video, layout) {
    const context = canvas.getContext("2d", { willReadFrequently: true });
    const maskCanvas = document.createElement("canvas");
    maskCanvas.width = canvas.width;
    maskCanvas.height = canvas.height;
    const maskContext = maskCanvas.getContext("2d", { willReadFrequently: true });
    if (!context || !maskContext) {
        throw new Error("MP4 Canvas 初始化失败");
    }

    const rgbFrame = scaledSourceFrame(layout.rgb_frame, layout, video);
    const alphaFrame = scaledSourceFrame(layout.alpha_frame, layout, video);
    return {
        destroy() {},
        draw() {
            context.clearRect(0, 0, canvas.width, canvas.height);
            maskContext.clearRect(0, 0, canvas.width, canvas.height);
            context.drawImage(video, ...rgbFrame, 0, 0, canvas.width, canvas.height);
            maskContext.drawImage(video, ...alphaFrame, 0, 0, canvas.width, canvas.height);
            const colorPixels = context.getImageData(0, 0, canvas.width, canvas.height);
            const maskPixels = maskContext.getImageData(0, 0, canvas.width, canvas.height).data;
            // 与 Android/iOS 客户端保持同一亮度与 smoothstep 阈值，后台看到的透明边缘才不会和 App 偏差。
            for (let index = 0; index < colorPixels.data.length; index += 4) {
                const luminance =
                    (maskPixels[index] * 0.299 + maskPixels[index + 1] * 0.587 + maskPixels[index + 2] * 0.114) / 255;
                const progress = clamp(
                    (luminance - alphaLowThreshold) / (alphaHighThreshold - alphaLowThreshold),
                    0,
                    1,
                );
                colorPixels.data[index + 3] = Math.round(progress * progress * (3 - 2 * progress) * 255);
            }
            context.putImageData(colorPixels, 0, 0);
        },
    };
}

function createProgram(gl, vertexSource, fragmentSource) {
    const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexSource);
    const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
    const program = gl.createProgram();
    if (!program) {
        throw new Error("MP4 WebGL Program 创建失败");
    }
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    gl.deleteShader(vertexShader);
    gl.deleteShader(fragmentShader);
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        const message = gl.getProgramInfoLog(program) || "MP4 WebGL Program 链接失败";
        gl.deleteProgram(program);
        throw new Error(message);
    }
    return program;
}

function compileShader(gl, type, source) {
    const shader = gl.createShader(type);
    if (!shader) {
        throw new Error("MP4 WebGL Shader 创建失败");
    }
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        const message = gl.getShaderInfoLog(shader) || "MP4 WebGL Shader 编译失败";
        gl.deleteShader(shader);
        throw new Error(message);
    }
    return shader;
}

function transparentLayoutKey(layout) {
    if (
        !layout ||
        layout.alpha_layout === "normal" ||
        !validFrame(layout.rgb_frame) ||
        !validFrame(layout.alpha_frame)
    ) {
        return "";
    }
    const videoWidth = positiveInteger(layout.video_w);
    const videoHeight = positiveInteger(layout.video_h);
    if (!videoWidth || !videoHeight) {
        return "";
    }
    return JSON.stringify({
        alpha_frame: layout.alpha_frame.slice(0, 4).map(Number),
        alpha_layout: layout.alpha_layout,
        rgb_frame: layout.rgb_frame.slice(0, 4).map(Number),
        video_h: videoHeight,
        video_w: videoWidth,
    });
}

function normalizedRect(frame, layout) {
    return new Float32Array([
        frame[0] / layout.video_w,
        frame[1] / layout.video_h,
        frame[2] / layout.video_w,
        frame[3] / layout.video_h,
    ]);
}

function scaledSourceFrame(frame, layout, video) {
    const scaleX = video.videoWidth / layout.video_w;
    const scaleY = video.videoHeight / layout.video_h;
    return [frame[0] * scaleX, frame[1] * scaleY, frame[2] * scaleX, frame[3] * scaleY];
}

function scaledOutputSize(width, height, maxEdge) {
    const scale = Math.min(1, maxEdge / Math.max(width, height));
    return {
        height: Math.max(1, Math.round(height * scale)),
        width: Math.max(1, Math.round(width * scale)),
    };
}

function validFrame(frame) {
    return (
        Array.isArray(frame) &&
        frame.length >= 4 &&
        frame
            .slice(0, 4)
            .every((value, index) => Number.isFinite(Number(value)) && (index < 2 ? value >= 0 : value > 0))
    );
}

function positiveInteger(value) {
    const number = Number(value);
    return Number.isInteger(number) && number > 0 ? number : 0;
}

function clamp(value, minimum, maximum) {
    return Math.min(maximum, Math.max(minimum, value));
}

function prefersReducedMotion() {
    return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
}

const vertexShaderSource = `
    attribute vec2 aPosition;
    varying vec2 vOutputUv;

    void main() {
        gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);
        vOutputUv = vec2(aPosition.x, 1.0 - aPosition.y);
    }
`;

const fragmentShaderSource = `
    precision mediump float;
    uniform sampler2D uTexture;
    uniform vec4 uRgbRect;
    uniform vec4 uAlphaRect;
    varying vec2 vOutputUv;

    vec2 mapRect(vec4 rect, vec2 uv) {
        return vec2(rect.x + uv.x * rect.z, rect.y + uv.y * rect.w);
    }

    void main() {
        vec4 color = texture2D(uTexture, mapRect(uRgbRect, vOutputUv));
        vec4 mask = texture2D(uTexture, mapRect(uAlphaRect, vOutputUv));
        float alpha = dot(mask.rgb, vec3(0.299, 0.587, 0.114));
        alpha = smoothstep(${alphaLowThreshold.toFixed(2)}, ${alphaHighThreshold.toFixed(2)}, alpha);
        gl_FragColor = vec4(color.rgb, alpha);
    }
`;
