(function () {
    async function checkEditorAvailable() {
        const LOCAL_EDITOR_PATH = '/openpose_editor_index';
        const REMOTE_EDITOR_PATH = 'https://huchenlei.github.io/sd-webui-openpose-editor/';

        async function testEditorPath(path) {
            const res = await fetch(path);
            return res.status === 200 ? path : null;
        }

        // Use local editor if the user has the extension installed. Fallback
        // onto remote editor if the local editor is not ready yet.
        // See https://github.com/huchenlei/sd-webui-openpose-editor/issues/53
        // for more details.
        return await testEditorPath(LOCAL_EDITOR_PATH) || await testEditorPath(REMOTE_EDITOR_PATH);
    }

    const cnetOpenposeEditorRegisteredElements = new Set();
    let editorURL = null;
    function loadOpenposeEditor() {
        // Simulate an `input` DOM event for Gradio Textbox component. Needed after you edit its contents in javascript, otherwise your edits
        // will only visible on web page and not sent to python.
        function updateInput(target) {
            let e = new Event("input", { bubbles: true })
            Object.defineProperty(e, "target", { value: target })
            target.dispatchEvent(e);
        }

        function navigateIframe(iframe, editorURL) {
            function getPathname(rawURL) {
                try {
                    return new URL(rawURL).pathname;
                } catch (e) {
                    return rawURL;
                }
            }

            return new Promise((resolve) => {
                const darkThemeParam = document.body.classList.contains('dark') ?
                    new URLSearchParams({ theme: 'dark' }).toString() :
                    '';

                window.addEventListener('message', (event) => {
                    const message = event.data;
                    if (message['ready']) resolve();
                }, { once: true });

                if ((editorURL.startsWith("http") ? iframe.src : getPathname(iframe.src)) !== editorURL) {
                    iframe.src = `${editorURL}?${darkThemeParam}`;
                    // By default assume 5 second is enough for the openpose editor
                    // to load.
                    setTimeout(resolve, 5000);
                } else {
                    // If no navigation is required, immediately return.
                    resolve();
                }
            });
        }
        const tabs = gradioApp().querySelectorAll('#controlnet .input-accordion');
        tabs.forEach(tab => {
            if (cnetOpenposeEditorRegisteredElements.has(tab)) return;
            cnetOpenposeEditorRegisteredElements.add(tab);

            const generatedImageGroup = tab.querySelector('.cnet-generated-image-group');
            const editButton = generatedImageGroup.querySelector('.cnet-edit-pose');

            editButton.addEventListener('click', async () => {
                const inputImageGroup = tab.querySelector('.cnet-input-image-group');
                const inputImage = inputImageGroup.querySelector('.cnet-image img');
                const downloadLink = generatedImageGroup.querySelector('.cnet-download-pose a');
                const modalId = editButton.id.replace('cnet-modal-open-', '');
                const modalIframe = generatedImageGroup.querySelector('.cnet-modal iframe');

                if (!editorURL) {
                    editorURL = await checkEditorAvailable();
                    if (!editorURL) {
                        alert("No openpose editor available.")
                    }
                }

                await navigateIframe(modalIframe, editorURL);
                modalIframe.contentWindow.postMessage({
                    modalId,
                    imageURL: inputImage ? inputImage.src : undefined,
                    poseURL: downloadLink.href,
                }, '*');
                // Focus the iframe so that the focus is no longer on the `Edit` button.
                // Pressing space when the focus is on `Edit` button will trigger
                // the click again to resend the frame message.
                modalIframe.contentWindow.focus();
            });
            /*
            * Writes the pose data URL to an link element on input image group.
            * Click a hidden button to trigger a backend rendering of the pose JSON.
            *
            * The backend should:
            * - Set the rendered pose image as preprocessor generated image.
            */
            function updatePreviewPose(poseURL) {
                const downloadLink = generatedImageGroup.querySelector('.cnet-download-pose a');
                const renderButton = generatedImageGroup.querySelector('.cnet-render-pose');
                const poseTextbox = generatedImageGroup.querySelector('.cnet-pose-json textarea');
                const allowPreviewCheckbox = tab.querySelector('.cnet-allow-preview input');

                if (!allowPreviewCheckbox.checked)
                    allowPreviewCheckbox.click();

                // Only set href when download link exists and needs an update. `downloadLink`
                // can be null when user closes preview and click `Upload JSON` button again.
                // https://github.com/Mikubill/sd-webui-controlnet/issues/2308
                if (downloadLink !== null)
                    downloadLink.href = poseURL;

                poseTextbox.value = poseURL;
                updateInput(poseTextbox);
                renderButton.click();
            }

            // Updates preview image when edit is done.
            window.addEventListener('message', (event) => {
                const message = event.data;
                const modalId = editButton.id.replace('cnet-modal-open-', '');
                if (message.modalId !== modalId) return;
                updatePreviewPose(message.poseURL);

                const closeModalButton = generatedImageGroup.querySelector('.cnet-modal .cnet-modal-close');
                closeModalButton.click();
            });

            const inputImageGroup = tab.querySelector('.cnet-input-image-group');
            const uploadButton = inputImageGroup.querySelector('.cnet-upload-pose input');
            // Updates preview image when JSON file is uploaded.
            uploadButton.addEventListener('change', (event) => {
                const file = event.target.files[0];
                if (!file)
                    return;

                const reader = new FileReader();
                reader.onload = function (e) {
                    const contents = e.target.result;
                    const poseURL = `data:application/json;base64,${btoa(contents)}`;
                    updatePreviewPose(poseURL);
                };
                reader.readAsText(file);
                // Reset the file input value so that uploading the same file still triggers callback.
                event.target.value = '';
            });
        });
    }

    onUiUpdate(loadOpenposeEditor);
})();