import { readAssetFps } from "../../utils/mediaFps.js";

const DEFAULT_FPS = 30;

type VideoFrameCallbackMedia = HTMLMediaElement & {
    requestVideoFrameCallback?: (callback: () => void) => number;
    cancelVideoFrameCallback?: (handle: number) => void;
};

function formatDuration(seconds: any) {
    const s = Number(seconds);
    if (!Number.isFinite(s) || s < 0) return "0:00";
    const total = Math.floor(s);
    const h = Math.floor(total / 3600);
    const m = Math.floor((total % 3600) / 60);
    const sec = total % 60;
    if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
    return `${m}:${String(sec).padStart(2, "0")}`;
}

function safePlay(media: any) {
    try {
        const p = media?.play?.();
        if (p && typeof p.catch === "function") p.catch(() => {});
    } catch (e: any) {
        console.debug?.(e);
    }
}

function clampFrame(frame: any, maxFrame: any) {
    const f = Math.floor(Number(frame) || 0);
    const max = Math.max(0, Math.floor(Number(maxFrame) || 0));
    if (f < 0) return 0;
    if (max > 0 && f > max) return max;
    return f;
}

function frameFromTime(video: any, fps: any) {
    const time = Number(video?.currentTime || 0);
    const rate = Number(fps) > 0 ? Number(fps) : DEFAULT_FPS;
    return Math.max(0, Math.floor(time * rate));
}

function maxFrameFromDuration(video: any, fps: any) {
    const duration = Number(video?.duration || 0);
    const rate = Number(fps) > 0 ? Number(fps) : DEFAULT_FPS;
    if (!Number.isFinite(duration) || duration <= 0) return 0;
    return Math.max(0, Math.floor(duration * rate));
}

function seekToFrame(video: any, frame: any, fps: any) {
    const rate = Number(fps) > 0 ? Number(fps) : DEFAULT_FPS;
    const maxFrame = maxFrameFromDuration(video, rate);
    const targetFrame = clampFrame(frame, maxFrame);
    const targetTime = targetFrame / rate;
    try {
        video.currentTime = Math.max(0, targetTime);
    } catch (e: any) {
        console.debug?.(e);
    }
}

function isTimelineMedia(mediaEl: any) {
    return mediaEl instanceof HTMLMediaElement;
}

function isVideoMedia(kind: any, mediaEl: any) {
    if (String(kind || "").toLowerCase() === "video") return true;
    return mediaEl instanceof HTMLVideoElement;
}

function isAudioMedia(kind: any, mediaEl: any) {
    if (String(kind || "").toLowerCase() === "audio") return true;
    return mediaEl instanceof HTMLAudioElement;
}

function isAnimatedImageKind(kind: any) {
    const k = String(kind || "").toLowerCase();
    return k === "gif" || k === "animated-image";
}

function tryBuildStaticSnapshotFromImage(imgEl: any) {
    try {
        const w = Number(imgEl?.naturalWidth || imgEl?.width || 0);
        const h = Number(imgEl?.naturalHeight || imgEl?.height || 0);
        if (!(w > 0 && h > 0)) return "";
        const canvas = document.createElement("canvas");
        canvas.width = w;
        canvas.height = h;
        const ctx = canvas.getContext("2d");
        if (!ctx) return "";
        ctx.drawImage(imgEl, 0, 0, w, h);
        return canvas.toDataURL("image/png");
    } catch (e: any) {
        console.debug?.(e);
        return "";
    }
}

export function mountFloatingViewerSimplePlayer(mediaEl: any, fileData: any = null, { kind = "" }: { kind?: string } = {}): Record<string, any> {
    if (!mediaEl || mediaEl._mjrSimplePlayerMounted) return mediaEl?.parentElement || null;
    mediaEl._mjrSimplePlayerMounted = true;

    const fps = readAssetFps(fileData) || DEFAULT_FPS;
    const timelineSupported = isTimelineMedia(mediaEl);
    const videoKind = isVideoMedia(kind, mediaEl);
    const audioKind = isAudioMedia(kind, mediaEl);
    const animatedImage = isAnimatedImageKind(kind);

    const root = document.createElement("div");
    root.className = "mjr-mfv-simple-player";
    root.tabIndex = 0;
    root.setAttribute("role", "group");
    root.setAttribute("aria-label", "Media player");
    if (audioKind) root.classList.add("is-audio");
    if (animatedImage) root.classList.add("is-animated-image");

    const controls = document.createElement("div");
    controls.className = "mjr-mfv-simple-player-controls";

    const seek = document.createElement("input");
    seek.type = "range";
    seek.className = "mjr-mfv-simple-player-seek";
    seek.min = "0";
    seek.max = "1000";
    seek.step = "1";
    seek.value = "0";
    seek.setAttribute("aria-label", "Seek");
    if (!timelineSupported) {
        seek.disabled = true;
        seek.classList.add("is-disabled");
    }

    const row = document.createElement("div");
    row.className = "mjr-mfv-simple-player-row";

    const playPauseBtn = document.createElement("button");
    playPauseBtn.type = "button";
    playPauseBtn.className = "mjr-icon-btn mjr-mfv-simple-player-btn";
    playPauseBtn.setAttribute("aria-label", "Play/Pause");
    const playPauseIcon = document.createElement("i");
    playPauseIcon.className = "pi pi-pause";
    playPauseIcon.setAttribute("aria-hidden", "true");
    playPauseBtn.appendChild(playPauseIcon);

    const stepBackBtn = document.createElement("button");
    stepBackBtn.type = "button";
    stepBackBtn.className = "mjr-icon-btn mjr-mfv-simple-player-btn";
    stepBackBtn.setAttribute("aria-label", "Step back");
    const stepBackIcon = document.createElement("i");
    stepBackIcon.className = "pi pi-step-backward";
    stepBackIcon.setAttribute("aria-hidden", "true");
    stepBackBtn.appendChild(stepBackIcon);

    const stepForwardBtn = document.createElement("button");
    stepForwardBtn.type = "button";
    stepForwardBtn.className = "mjr-icon-btn mjr-mfv-simple-player-btn";
    stepForwardBtn.setAttribute("aria-label", "Step forward");
    const stepForwardIcon = document.createElement("i");
    stepForwardIcon.className = "pi pi-step-forward";
    stepForwardIcon.setAttribute("aria-hidden", "true");
    stepForwardBtn.appendChild(stepForwardIcon);

    const timeLabel = document.createElement("div");
    timeLabel.className = "mjr-mfv-simple-player-time";
    timeLabel.textContent = "0:00 / 0:00";

    const frameLabel = document.createElement("div");
    frameLabel.className = "mjr-mfv-simple-player-frame";
    frameLabel.textContent = "F: 0";
    if (!videoKind) frameLabel.style.display = "none";

    const muteBtn = document.createElement("button");
    muteBtn.type = "button";
    muteBtn.className = "mjr-icon-btn mjr-mfv-simple-player-btn";
    muteBtn.setAttribute("aria-label", "Mute/Unmute");
    const muteIcon = document.createElement("i");
    muteIcon.className = "pi pi-volume-up";
    muteIcon.setAttribute("aria-hidden", "true");
    muteBtn.appendChild(muteIcon);

    if (!videoKind) {
        stepBackBtn.disabled = true;
        stepForwardBtn.disabled = true;
        stepBackBtn.classList.add("is-disabled");
        stepForwardBtn.classList.add("is-disabled");
    }

    row.appendChild(stepBackBtn);
    row.appendChild(playPauseBtn);
    row.appendChild(stepForwardBtn);
    row.appendChild(timeLabel);
    row.appendChild(frameLabel);
    row.appendChild(muteBtn);

    controls.appendChild(seek);
    controls.appendChild(row);

    root.appendChild(mediaEl);
    if (audioKind) {
        const audioBackdrop = document.createElement("div");
        audioBackdrop.className = "mjr-mfv-simple-player-audio-backdrop";
        audioBackdrop.textContent = String(fileData?.filename || "Audio");
        root.appendChild(audioBackdrop);
    }
    root.appendChild(controls);

    try {
        if (mediaEl instanceof HTMLMediaElement) {
            mediaEl.controls = false;
            (mediaEl as HTMLVideoElement).playsInline = true;
            mediaEl.loop = true;
            mediaEl.muted = true;
            mediaEl.autoplay = true;
        }
    } catch (e: any) {
        console.debug?.(e);
    }

    const animatedSrc = animatedImage ? String(mediaEl?.src || "") : "";
    let animatedPaused = false;
    let animatedSnapshot = "";
    let syncRafId: any = null;
    let syncRvfcId: any = null;

    const updatePlayPauseIcon = () => {
        if (timelineSupported) {
            playPauseIcon.className = mediaEl.paused ? "pi pi-play" : "pi pi-pause";
            return;
        }
        if (animatedImage) {
            playPauseIcon.className = animatedPaused ? "pi pi-play" : "pi pi-pause";
            return;
        }
        playPauseIcon.className = "pi pi-play";
    };

    const updateMuteIcon = () => {
        if (mediaEl instanceof HTMLMediaElement) {
            muteIcon.className = mediaEl.muted ? "pi pi-volume-off" : "pi pi-volume-up";
            return;
        }
        muteIcon.className = "pi pi-volume-off";
        muteBtn.disabled = true;
        muteBtn.classList.add("is-disabled");
    };

    const updateFrameLabel = () => {
        if (!videoKind || !(mediaEl instanceof HTMLVideoElement)) return;
        const current = frameFromTime(mediaEl, fps);
        const max = maxFrameFromDuration(mediaEl, fps);
        frameLabel.textContent = max > 0 ? `F: ${current}/${max}` : `F: ${current}`;
    };

    const updateSeekProgressPaint = () => {
        const pct = Math.max(0, Math.min(100, (Number(seek.value) / 1000) * 100));
        seek.style.setProperty("--mjr-seek-pct", `${pct}%`);
    };

    const updateTimeAndSeek = () => {
        if (!timelineSupported) {
            timeLabel.textContent = animatedImage ? "Animated" : "Preview";
            seek.value = "0";
            updateSeekProgressPaint();
            return;
        }
        const currentTime = Number(mediaEl.currentTime || 0);
        const duration = Number(mediaEl.duration || 0);
        if (Number.isFinite(duration) && duration > 0) {
            const ratio = Math.max(0, Math.min(1, currentTime / duration));
            seek.value = String(Math.round(ratio * 1000));
            timeLabel.textContent = `${formatDuration(currentTime)} / ${formatDuration(duration)}`;
        } else {
            seek.value = "0";
            timeLabel.textContent = `${formatDuration(currentTime)} / 0:00`;
        }
        updateSeekProgressPaint();
    };

    const cancelPlaybackUiSync = () => {
        try {
            const rvfcMedia = mediaEl as VideoFrameCallbackMedia | null;
            if (syncRvfcId != null && typeof rvfcMedia?.cancelVideoFrameCallback === "function") {
                rvfcMedia.cancelVideoFrameCallback(syncRvfcId);
            }
        } catch (err: any) {
            console.debug?.(err);
        }
        syncRvfcId = null;
        try {
            if (syncRafId != null && typeof cancelAnimationFrame === "function") {
                cancelAnimationFrame(syncRafId);
            }
        } catch (err: any) {
            console.debug?.(err);
        }
        syncRafId = null;
    };

    const tickPlaybackUiSync = () => {
        syncRafId = null;
        syncRvfcId = null;
        try {
            updateFrameLabel();
            updateTimeAndSeek();
        } catch (err: any) {
            console.debug?.(err);
        }
        if (!timelineSupported || mediaEl?.paused) return;
        try {
            const rvfcMedia = mediaEl as VideoFrameCallbackMedia | null;
            if (typeof rvfcMedia?.requestVideoFrameCallback === "function") {
                syncRvfcId = rvfcMedia.requestVideoFrameCallback(tickPlaybackUiSync);
                return;
            }
        } catch (err: any) {
            console.debug?.(err);
        }
        try {
            if (typeof requestAnimationFrame === "function") {
                syncRafId = requestAnimationFrame(tickPlaybackUiSync);
            }
        } catch (err: any) {
            console.debug?.(err);
        }
    };

    const startPlaybackUiSync = () => {
        cancelPlaybackUiSync();
        if (!timelineSupported || mediaEl?.paused) return;
        tickPlaybackUiSync();
    };

    const stopEvent = (e: any) => {
        try {
            e?.stopPropagation?.();
        } catch {
            // ignore
        }
    };

    const onPlayPause = (e: any) => {
        stopEvent(e);
        try {
            if (timelineSupported) {
                if (mediaEl.paused) safePlay(mediaEl);
                else mediaEl.pause?.();
            } else if (animatedImage) {
                if (!animatedPaused) {
                    if (!animatedSnapshot)
                        animatedSnapshot = tryBuildStaticSnapshotFromImage(mediaEl);
                    if (animatedSnapshot) mediaEl.src = animatedSnapshot;
                    animatedPaused = true;
                } else {
                    const restartSrc = animatedSrc
                        ? `${animatedSrc}${animatedSrc.includes("?") ? "&" : "?"}mjr_anim=${Date.now()}`
                        : mediaEl.src;
                    mediaEl.src = restartSrc;
                    animatedPaused = false;
                }
            }
        } catch (err: any) {
            console.debug?.(err);
        }
        updatePlayPauseIcon();
    };

    const onStep = (delta: any, e: any) => {
        stopEvent(e);
        if (!videoKind || !(mediaEl instanceof HTMLVideoElement)) return;
        try {
            mediaEl.pause?.();
        } catch (err: any) {
            console.debug?.(err);
        }
        const current = frameFromTime(mediaEl, fps);
        seekToFrame(mediaEl, current + delta, fps);
        updatePlayPauseIcon();
        updateFrameLabel();
        updateTimeAndSeek();
    };

    const onMute = (e: any) => {
        stopEvent(e);
        if (!(mediaEl instanceof HTMLMediaElement)) return;
        try {
            mediaEl.muted = !mediaEl.muted;
        } catch (err: any) {
            console.debug?.(err);
        }
        updateMuteIcon();
    };

    const onSeekInput = (e: any) => {
        stopEvent(e);
        if (!timelineSupported) return;
        updateSeekProgressPaint();
        const duration = Number(mediaEl.duration || 0);
        if (!Number.isFinite(duration) || duration <= 0) return;
        const ratio = Math.max(0, Math.min(1, Number(seek.value) / 1000));
        try {
            mediaEl.currentTime = duration * ratio;
        } catch (err: any) {
            console.debug?.(err);
        }
        updateFrameLabel();
        updateTimeAndSeek();
    };

    const stopControlEvent = (e: any) => stopEvent(e);

    const focusPlayerRoot = (e: any) => {
        try {
            if (e?.target?.closest?.("button, input, textarea, select")) return;
            root.focus?.({ preventScroll: true });
        } catch (err: any) {
            console.debug?.(err);
        }
    };

    const onPlayerKeydown = (e: any) => {
        const key = String(e?.key || "");
        if (!key || e?.altKey || e?.ctrlKey || e?.metaKey) return;

        if (key === " " || key === "Spacebar") {
            e.preventDefault?.();
            onPlayPause(e);
            return;
        }

        if (key === "ArrowLeft") {
            if (!videoKind) return;
            e.preventDefault?.();
            onStep(-1, e);
            return;
        }

        if (key === "ArrowRight") {
            if (!videoKind) return;
            e.preventDefault?.();
            onStep(1, e);
        }
    };

    try {
        (root as any)._mjrSimplePlayerHandleKeydown = onPlayerKeydown;
    } catch (e: any) {
        console.debug?.(e);
    }

    playPauseBtn.addEventListener("click", onPlayPause);
    stepBackBtn.addEventListener("click", (e: any) => onStep(-1, e));
    stepForwardBtn.addEventListener("click", (e: any) => onStep(1, e));
    muteBtn.addEventListener("click", onMute);
    seek.addEventListener("input", onSeekInput);

    controls.addEventListener("pointerdown", stopControlEvent);
    controls.addEventListener("click", stopControlEvent);
    controls.addEventListener("dblclick", stopControlEvent);
    root.addEventListener("pointerdown", focusPlayerRoot);
    root.addEventListener("keydown", onPlayerKeydown);

    if (mediaEl instanceof HTMLMediaElement) {
        mediaEl.addEventListener(
            "play",
            () => {
                updatePlayPauseIcon();
                startPlaybackUiSync();
            },
            { passive: true },
        );
        mediaEl.addEventListener(
            "pause",
            () => {
                cancelPlaybackUiSync();
                updatePlayPauseIcon();
                updateFrameLabel();
                updateTimeAndSeek();
            },
            { passive: true },
        );
        mediaEl.addEventListener(
            "ended",
            () => {
                cancelPlaybackUiSync();
                updatePlayPauseIcon();
                updateFrameLabel();
                updateTimeAndSeek();
            },
            { passive: true },
        );
        mediaEl.addEventListener(
            "timeupdate",
            () => {
                updateFrameLabel();
                updateTimeAndSeek();
            },
            { passive: true },
        );
        mediaEl.addEventListener(
            "seeked",
            () => {
                updateFrameLabel();
                updateTimeAndSeek();
            },
            { passive: true },
        );
        mediaEl.addEventListener(
            "loadedmetadata",
            () => {
                updateFrameLabel();
                updateTimeAndSeek();
            },
            { passive: true },
        );
    }

    safePlay(mediaEl);
    updatePlayPauseIcon();
    updateMuteIcon();
    updateFrameLabel();
    updateTimeAndSeek();
    startPlaybackUiSync();

    try {
        (root as any)._mjrSimplePlayerDestroy = cancelPlaybackUiSync;
        (root as any)._mjrMediaControlsHandle = { destroy: cancelPlaybackUiSync };
    } catch (e: any) {
        console.debug?.(e);
    }

    return root;
}
