import { afterEach, describe, expect, test, vi } from "vitest";
import {
    confirmMp4AlphaLayoutMetadata,
    createMp4AlphaLayout,
    detectMp4AlphaLayoutFromFrames,
    isMp4Source,
    mp4AlphaLayoutFromMetadata,
    mp4FileFromSource,
    updateMp4AlphaLayoutFrame,
    updateMp4AlphaLayoutKind,
} from "./mp4AlphaLayout.js";

afterEach(() => {
    vi.unstubAllGlobals();
});

describe("mp4 alpha layout metadata", () => {
    test("builds a confirmed normal layout for a full-frame MP4", () => {
        const layout = createMp4AlphaLayout({
            alphaLayout: "normal",
            confirmed: true,
            videoH: 1334,
            videoW: 750,
        });

        expect(layout).toMatchObject({
            alpha_frame: null,
            alpha_layout: "normal",
            confirmed: true,
            rgb_frame: [0, 0, 750, 1334],
            video_h: 1334,
            video_w: 750,
        });
    });

    test("builds left-alpha and right-rgb frames for a 1500x1334 MP4", () => {
        const layout = createMp4AlphaLayout({
            alphaLayout: "alpha_left_rgb_right",
            confirmed: true,
            videoH: 1334,
            videoW: 1500,
        });

        expect(layout).toMatchObject({
            alpha_frame: [0, 0, 750, 1334],
            alpha_layout: "alpha_left_rgb_right",
            rgb_frame: [750, 0, 750, 1334],
            video_h: 1334,
            video_w: 1500,
        });
    });

    test("builds split frames for an uneven 1472x1328 MP4", () => {
        const layout = createMp4AlphaLayout({
            alphaLayout: "rgb_left_alpha_right",
            confirmed: true,
            videoH: 1328,
            videoW: 1472,
        });

        expect(layout).toMatchObject({
            alpha_frame: [736, 0, 736, 1328],
            alpha_layout: "rgb_left_alpha_right",
            rgb_frame: [0, 0, 736, 1328],
        });
    });

    test("keeps custom rgb and alpha rectangles", () => {
        const layout = createMp4AlphaLayout({
            alphaFrame: [10, 20, 200, 300],
            alphaLayout: "custom",
            confirmed: true,
            rgbFrame: [240, 20, 200, 300],
            videoH: 812,
            videoW: 750,
        });

        expect(layout).toMatchObject({
            alpha_frame: [10, 20, 200, 300],
            alpha_layout: "custom",
            rgb_frame: [240, 20, 200, 300],
        });
    });

    test("marks metadata unconfirmed when layout or frames are edited", () => {
        const metadataJson = confirmMp4AlphaLayoutMetadata(
            JSON.stringify({
                animation_format: "mp4",
                mp4_alpha_layout: createMp4AlphaLayout({
                    alphaLayout: "alpha_left_rgb_right",
                    confirmed: false,
                    videoH: 1334,
                    videoW: 1500,
                }),
            }),
        );

        expect(mp4AlphaLayoutFromMetadata(metadataJson)?.confirmed).toBe(true);
        expect(mp4AlphaLayoutFromMetadata(updateMp4AlphaLayoutKind(metadataJson, "normal"))).toMatchObject({
            alpha_layout: "normal",
            confirmed: false,
        });
        expect(
            mp4AlphaLayoutFromMetadata(updateMp4AlphaLayoutFrame(metadataJson, "rgb_frame", "760,0,700,1334")),
        ).toMatchObject({
            confirmed: false,
            rgb_frame: [760, 0, 700, 1334],
        });
    });

    test("detects and downloads mp4 source urls for row recalculation", async () => {
        const fetchMock = vi.fn(async () => ({
            blob: async () => new Blob(["mp4"], { type: "video/mp4" }),
            ok: true,
        }));
        vi.stubGlobal("fetch", fetchMock);

        expect(isMp4Source("https://cdn.example.test/assets/礼物_love_animation.mp4?token=1")).toBe(true);
        expect(isMp4Source("https://cdn.example.test/assets/rose.svga")).toBe(false);

        const file = await mp4FileFromSource("https://cdn.example.test/assets/%E7%A4%BC%E7%89%A9.mp4?token=1");

        expect(fetchMock).toHaveBeenCalledWith("https://cdn.example.test/assets/%E7%A4%BC%E7%89%A9.mp4?token=1");
        expect(file.name).toBe("礼物.mp4");
        expect(file.type).toBe("video/mp4");
    });

    test("keeps a regular 750x1334 portrait MP4 as normal layout", () => {
        const layout = detectMp4AlphaLayoutFromFrames([
            buildSyntheticFrame(750, 1334, {
                alphaFrame: [0, 0, 375, 1334],
                rgbFrame: [375, 0, 375, 1334],
            }),
        ]);

        expect(layout).toMatchObject({
            alpha_frame: null,
            alpha_layout: "normal",
            rgb_frame: [0, 0, 750, 1334],
            video_h: 1334,
            video_w: 750,
        });
    });

    test("detects a wide left-alpha right-rgb MP4 layout", () => {
        const layout = detectMp4AlphaLayoutFromFrames([
            buildSyntheticFrame(1500, 1334, {
                alphaFrame: [0, 0, 750, 1334],
                rgbFrame: [750, 0, 750, 1334],
            }),
        ]);

        expect(layout).toMatchObject({
            alpha_frame: [0, 0, 750, 1334],
            alpha_layout: "alpha_left_rgb_right",
            rgb_frame: [750, 0, 750, 1334],
            video_h: 1334,
            video_w: 1500,
        });
    });
});

function buildSyntheticFrame(width, height, { alphaFrame, rgbFrame }) {
    const data = new Uint8ClampedArray(width * height * 4);
    fillRect(data, width, [0, 0, width, height], [32, 32, 32]);
    fillCheckerRect(data, width, alphaFrame, [0, 0, 0], [255, 255, 255]);
    fillCheckerRect(data, width, rgbFrame, [230, 30, 20], [20, 150, 240]);
    return { data, height, width };
}

function fillRect(data, canvasWidth, rect, color) {
    const [left, top, width, height] = rect;
    for (let y = top; y < top + height; y += 1) {
        for (let x = left; x < left + width; x += 1) {
            const offset = (y * canvasWidth + x) * 4;
            data[offset] = color[0];
            data[offset + 1] = color[1];
            data[offset + 2] = color[2];
            data[offset + 3] = 255;
        }
    }
}

function fillCheckerRect(data, canvasWidth, rect, firstColor, secondColor) {
    const [left, top, width, height] = rect;
    for (let y = top; y < top + height; y += 1) {
        for (let x = left; x < left + width; x += 1) {
            const color =
                (Math.floor(x / 12) + Math.floor(y / 12)) % 2 === 0 ? firstColor : secondColor;
            const offset = (y * canvasWidth + x) * 4;
            data[offset] = color[0];
            data[offset + 1] = color[1];
            data[offset + 2] = color[2];
            data[offset + 3] = 255;
        }
    }
}
