window.SD = (() => {
  /*
   * Painterro is made a field of the SD global object
   * To provide convinience when using w() method in css_and_js.py
   */
  class PainterroClass {
    static isOpen = false;
    static async init ({ x, toId }) {
      console.log(x)

      const originalImage = x[2] === 'Mask' ? x[1]?.image : x[0];

      if (window.Painterro === undefined) {
        try {
          await this.load();
        } catch (e) {
          SDClass.error(e);

          return this.fallback(originalImage);
        }
      }

      if (this.isOpen) {
        return this.fallback(originalImage);
      }
      this.isOpen = true;

      let resolveResult;
      const paintClient = Painterro({
        hiddenTools: ['arrow'],
        onHide: () => {
          resolveResult?.(null);
        },
        saveHandler: (image, done) => {
          const data = image.asDataURL();

          // ensures stable performance even
          // when the editor is in interactive mode
          SD.clearImageInput(SD.el.get(`#${toId}`));

          resolveResult(data);

          done(true);
          paintClient.hide();
        },
      });

      const result = await new Promise((resolve) => {
        resolveResult = resolve;
        paintClient.show(originalImage);
      });
      this.isOpen = false;

      return result ? this.success(result) : this.fallback(originalImage);
    }
    static success (result) { return [result, { image: result, mask: result }] };
    static fallback (image) { return [image, { image: image, mask: image }] };
    static load () {
      return new Promise((resolve, reject) => {
        const scriptId = '__painterro-script';
        if (document.getElementById(scriptId)) {
          reject(new Error('Tried to load painterro script, but script tag already exists.'));
          return;
        }

        const styleId = '__painterro-css-override';
        if (!document.getElementById(styleId)) {
          /* Ensure Painterro window is always on top */
          const style = document.createElement('style');
          style.id = styleId;
          style.setAttribute('type', 'text/css');
          style.appendChild(document.createTextNode(`
            .ptro-holder-wrapper {
                z-index: 100;
            }
          `));
          document.head.appendChild(style);
        }

        const script = document.createElement('script');
        script.id = scriptId;
        script.src = 'https://unpkg.com/painterro@1.2.78/build/painterro.min.js';
        script.onload = () => resolve(true);
        script.onerror = (e) => {
          // remove self on error to enable reattempting load
          document.head.removeChild(script);
          reject(e);
        };
        document.head.appendChild(script);
      });
    }
  }

  /*
   * Turns out caching elements doesn't actually work in gradio
   * As elements in tabs might get recreated
   */
  class ElementCache {
    #el;
    constructor () {
      this.root = document.querySelector('gradio-app').shadowRoot;
    }
    get (selector) {
      return this.root.querySelector(selector);
    }
  }

  /*
   * The main helper class to incapsulate functions
   * that change gradio ui functionality
   */
  class SDClass {
    el = new ElementCache();
    Painterro = PainterroClass;
    moveImageFromGallery ({ x, fromId, toId }) {
      x = x[0];
      if (!Array.isArray(x) || x.length === 0) return;

      this.clearImageInput(this.el.get(`#${toId}`));

      const i = this.#getGallerySelectedIndex(this.el.get(`#${fromId}`));

      return [x[i].replace('data:;','data:image/png;')];
    }
    async copyImageFromGalleryToClipboard ({ x, fromId }) {
      x = x[0];
      if (!Array.isArray(x) || x.length === 0) return;

      const i = this.#getGallerySelectedIndex(this.el.get(`#${fromId}`));

      const data = x[i];
      const blob = await (await fetch(data.replace('data:;','data:image/png;'))).blob();
      const item = new ClipboardItem({'image/png': blob});

      await this.copyToClipboard([item]);
    }
    clickFirstVisibleButton({ rowId }) {
      const generateButtons = this.el.get(`#${rowId}`).querySelectorAll('.gr-button-primary');

      if (!generateButtons) return;

      for (let i = 0, arr = [...generateButtons]; i < arr.length; i++) {
        const cs = window.getComputedStyle(arr[i]);

        if (cs.display !== 'none' && cs.visibility !== 'hidden') {
          console.log(arr[i]);

          arr[i].click();
          break;
        }
      }
    }
    async gradioInputToClipboard ({ x }) { return this.copyToClipboard(x[0]); }
    async copyToClipboard (value) {
      if (!value || typeof value === 'boolean') return;
      try {
        if (Array.isArray(value) &&
            value.length &&
            value[0] instanceof ClipboardItem) {
          await navigator.clipboard.write(value);
        } else {
          await navigator.clipboard.writeText(value);
        }
      } catch (e) {
        SDClass.error(e);
      }
    }
    static error (e) {
      console.error(e);
      if (typeof e === 'string') {
        alert(e);
      } else if(typeof e === 'object' && Object.hasOwn(e, 'message')) {
        alert(e.message);
      }
    }
    clearImageInput (imageEditor) {
      imageEditor?.querySelector('.modify-upload button:last-child')?.click();
    }
    #getGallerySelectedIndex (gallery) {
      const selected = gallery.querySelector(`.\\!ring-2`);
      return selected ? [...selected.parentNode.children].indexOf(selected) : 0;
    }
  }

  return new SDClass();
})();