const encodeForm =
    document.getElementById("encode-form");

const decodeForm =
    document.getElementById("decode-form");


const encodeImage =
    document.getElementById("encode-image");

const decodeImage =
    document.getElementById("decode-image");


const encodeDropzone =
    document.getElementById("encode-dropzone");

const decodeDropzone =
    document.getElementById("decode-dropzone");


const encodeImageInfo =
    document.getElementById("encode-image-info");

const decodeImageInfo =
    document.getElementById("decode-image-info");


const messageSection =
    document.getElementById("message-section");

const fileSection =
    document.getElementById("file-section");


const messageInput =
    document.getElementById("message");

const messageSize =
    document.getElementById("message-size");

const secretFile =
    document.getElementById("secret-file");


const encodePassword =
    document.getElementById("encode-password");

const decodePassword =
    document.getElementById("decode-password");


const encodeButton =
    document.getElementById("encode-button");

const decodeButton =
    document.getElementById("decode-button");


const encodeStatus =
    document.getElementById("encode-status");

const decodeStatus =
    document.getElementById("decode-status");


const decodedMessage =
    document.getElementById("decoded-message");

const messageOutput =
    document.getElementById("message-output");

const copyMessage =
    document.getElementById("copy-message");


function getEncodeMode() {
    const selected =
        document.querySelector(
            'input[name="encode-mode"]:checked'
        );

    return selected
        ? selected.value
        : "message";
}


function setStatus(
    element,
    message,
    type = ""
) {
    element.textContent =
        message;

    element.className =
        `status ${type}`;
}


function setLoading(
    button,
    loading
) {
    if (loading) {

        button.disabled =
            true;

        button.dataset.originalText =
            button.textContent;

        button.textContent =
            "Working...";

    } else {

        button.disabled =
            false;

        button.textContent =
            button.dataset.originalText ||
            button.textContent;
    }
}


function formatBytes(bytes) {

    if (bytes < 1024) {
        return `${bytes} B`;
    }

    if (bytes < 1024 * 1024) {
        return `${(bytes / 1024).toFixed(1)} KB`;
    }

    return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}


function setSelectedFile(
    input,
    file
) {
    /*
     * DataTransfer lets us put a file selected
     * through drag-and-drop into the file input.
     */
    const dataTransfer =
        new DataTransfer();

    dataTransfer.items.add(file);

    input.files =
        dataTransfer.files;

    input.dispatchEvent(
        new Event(
            "change",
            {
                bubbles: true
            }
        )
    );
}


function setupDropzone(
    dropzone,
    input
) {

    dropzone.addEventListener(
        "click",
        () => {
            input.click();
        }
    );


    dropzone.addEventListener(
        "keydown",
        (event) => {

            if (
                event.key === "Enter" ||
                event.key === " "
            ) {

                event.preventDefault();

                input.click();
            }
        }
    );


    [
        "dragenter",
        "dragover"
    ].forEach((eventName) => {

        dropzone.addEventListener(
            eventName,
            (event) => {

                event.preventDefault();

                dropzone.classList.add(
                    "dragging"
                );
            }
        );
    });


    [
        "dragleave",
        "drop"
    ].forEach((eventName) => {

        dropzone.addEventListener(
            eventName,
            (event) => {

                event.preventDefault();

                dropzone.classList.remove(
                    "dragging"
                );
            }
        );
    });


    dropzone.addEventListener(
        "drop",
        (event) => {

            const file =
                event.dataTransfer.files[0];

            if (!file) {
                return;
            }

            if (
                !file.type.includes("png") &&
                !file.name
                    .toLowerCase()
                    .endsWith(".png")
            ) {

                alert(
                    "Please select a PNG image."
                );

                return;
            }

            setSelectedFile(
                input,
                file
            );
        }
    );


    input.addEventListener(
        "change",
        () => {

            const file =
                input.files[0];

            if (!file) {
                return;
            }

            dropzone.classList.add(
                "has-file"
            );

            const strong =
                dropzone.querySelector(
                    "strong"
                );

            const span =
                dropzone.querySelector(
                    "span"
                );

            strong.textContent =
                file.name;

            span.textContent =
                formatBytes(file.size);
        }
    );
}


setupDropzone(
    encodeDropzone,
    encodeImage
);

setupDropzone(
    decodeDropzone,
    decodeImage
);


async function getImageInfo(file) {

    const form =
        new FormData();

    form.append(
        "image",
        file
    );


    const response =
        await fetch(
            "/api/info",
            {
                method: "POST",
                body: form
            }
        );


    const data =
        await response.json();


    if (!response.ok) {

        throw new Error(
            data.error ||
            "Unable to inspect image"
        );
    }


    return data;
}


async function updateImageInfo(
    file,
    element,
    statusElement
) {

    element.classList.add(
        "hidden"
    );

    if (!file) {
        return;
    }


    try {

        const info =
            await getImageInfo(file);


        element.innerHTML =
            `
            <strong>
                ${info.width} × ${info.height}
            </strong>

            <span>
                ${info.format.toUpperCase()}
                · ${info.capacityKB} KB capacity
            </span>
            `;


        element.classList.remove(
            "hidden"
        );

    } catch (error) {

        setStatus(
            statusElement,
            error.message,
            "error"
        );
    }
}


encodeImage.addEventListener(
    "change",
    async () => {

        await updateImageInfo(
            encodeImage.files[0],
            encodeImageInfo,
            encodeStatus
        );
    }
);


messageInput.addEventListener(
    "input",
    () => {

        const count =
            messageInput.value.length;

        messageSize.textContent =
            `${count.toLocaleString()} character${
                count === 1
                    ? ""
                    : "s"
            }`;
    }
);


document
    .querySelectorAll(
        'input[name="encode-mode"]'
    )
    .forEach((radio) => {

        radio.addEventListener(
            "change",
            () => {

                const mode =
                    getEncodeMode();


                if (
                    mode === "message"
                ) {

                    messageSection
                        .classList
                        .remove("hidden");

                    fileSection
                        .classList
                        .add("hidden");

                } else {

                    messageSection
                        .classList
                        .add("hidden");

                    fileSection
                        .classList
                        .remove("hidden");
                }
            }
        );
    });


encodeForm.addEventListener(
    "submit",
    async (event) => {

        event.preventDefault();


        const image =
            encodeImage.files[0];

        const password =
            encodePassword.value;

        const mode =
            getEncodeMode();


        if (!image) {

            setStatus(
                encodeStatus,
                "Please select a PNG image.",
                "error"
            );

            return;
        }


        const form =
            new FormData();

        form.append(
            "image",
            image
        );

        /*
         * Password is optional.
         */
        if (password) {

            form.append(
                "password",
                password
            );
        }


        if (
            mode === "message"
        ) {

            if (
                !messageInput.value
            ) {

                setStatus(
                    encodeStatus,
                    "Please enter a message.",
                    "error"
                );

                return;
            }


            form.append(
                "message",
                messageInput.value
            );

        } else {

            const file =
                secretFile.files[0];


            if (!file) {

                setStatus(
                    encodeStatus,
                    "Please select a file.",
                    "error"
                );

                return;
            }


            form.append(
                "file",
                file
            );
        }


        setLoading(
            encodeButton,
            true
        );


        setStatus(
            encodeStatus,
            "Encoding..."
        );


        try {

            const response =
                await fetch(
                    "/api/encode",
                    {
                        method: "POST",
                        body: form
                    }
                );


            if (!response.ok) {

                const data =
                    await response.json();

                throw new Error(
                    data.error ||
                    "Encoding failed"
                );
            }


            const blob =
                await response.blob();


            const url =
                URL.createObjectURL(
                    blob
                );


            const link =
                document.createElement(
                    "a"
                );


            link.href =
                url;

            link.download =
                "encoded.png";


            document.body.appendChild(
                link
            );

            link.click();

            link.remove();


            URL.revokeObjectURL(
                url
            );


            setStatus(
                encodeStatus,
                "Encoded successfully. Download started.",
                "success"
            );

        } catch (error) {

            setStatus(
                encodeStatus,
                error.message,
                "error"
            );

        } finally {

            setLoading(
                encodeButton,
                false
            );
        }
    }
);


decodeForm.addEventListener(
    "submit",
    async (event) => {

        event.preventDefault();


        const image =
            decodeImage.files[0];

        const password =
            decodePassword.value;


        decodedMessage
            .classList
            .add("hidden");


        if (!image) {

            setStatus(
                decodeStatus,
                "Please select an encoded PNG.",
                "error"
            );

            return;
        }


        const form =
            new FormData();


        form.append(
            "image",
            image
        );


        /*
         * Password is optional.
         */
        if (password) {

            form.append(
                "password",
                password
            );
        }


        setLoading(
            decodeButton,
            true
        );


        setStatus(
            decodeStatus,
            "Decoding..."
        );


        try {

            const response =
                await fetch(
                    "/api/decode",
                    {
                        method: "POST",
                        body: form
                    }
                );


            if (!response.ok) {

                const data =
                    await response.json();

                throw new Error(
                    data.error ||
                    "Decoding failed"
                );
            }


            const contentType =
                response.headers.get(
                    "content-type"
                );


            /*
             * Message payload.
             */
            if (
                contentType &&
                contentType.startsWith(
                    "application/json"
                )
            ) {

                const result =
                    await response.json();


                if (
                    result.type !==
                    "message"
                ) {

                    throw new Error(
                        "Unknown decoded payload"
                    );
                }


                messageOutput.textContent =
                    result.message;


                decodedMessage
                    .classList
                    .remove("hidden");


                setStatus(
                    decodeStatus,
                    "Message decoded successfully.",
                    "success"
                );


                return;
            }


            /*
             * File payload.
             */

            const blob =
                await response.blob();


            const url =
                URL.createObjectURL(
                    blob
                );


            let filename =
                "extracted-file";


            const disposition =
                response.headers.get(
                    "content-disposition"
                );


            if (disposition) {

                const match =
                    disposition.match(
                        /filename="([^"]+)"/
                    );


                if (match) {
                    filename =
                        match[1];
                }
            }


            const link =
                document.createElement(
                    "a"
                );


            link.href =
                url;

            link.download =
                filename;


            document.body.appendChild(
                link
            );

            link.click();

            link.remove();


            URL.revokeObjectURL(
                url
            );


            setStatus(
                decodeStatus,
                `File extracted: ${filename}`,
                "success"
            );

        } catch (error) {

            setStatus(
                decodeStatus,
                error.message,
                "error"
            );

        } finally {

            setLoading(
                decodeButton,
                false
            );
        }
    }
);


copyMessage.addEventListener(
    "click",
    async () => {

        try {

            await navigator.clipboard.writeText(
                messageOutput.textContent
            );


            const original =
                copyMessage.textContent;

            copyMessage.textContent =
                "Copied!";


            setTimeout(
                () => {
                    copyMessage.textContent =
                        original;
                },
                1500
            );

        } catch {

            setStatus(
                decodeStatus,
                "Unable to copy message.",
                "error"
            );
        }
    }
);