class PleromaModLoader {
  constructor () {
    this.config = {
      "modDirectory": "/instance/pleroma-mods/",
      "mods": []
    };
    this.loadConfig();
    this.loadedMods = {};
    this.classes = {};
  }

  loadMods () {
    this.config.mods.forEach((mod) => {
      this.loadMod(mod);
    });
  }

  loadMod (modName) {
    return PleromaModLoader.includeScript(
      this.config.modDirectory + "pleroma-mod-" + modName + "/mod.js"
    ).then(() => {
      this.loadedMods[modName] = new this.classes[PleromaModLoader.getClassName(modName)]();
    });
  }

  loadConfig () {
    window.fetch("/instance/pleroma-mod-config.json").then((response) => {
      return response.json();
    }).then((json) => {
      Object.keys(json).forEach((key) => {
        this.config[key] = json[key];
      });
      this.loadMods();
    }).catch((error) => {
      console.error("can't load loader config");
      console.error(error);
    });
  }

  registerClass (className, object) {
    this.classes[className] = object;
  }

  waitUntilReady () {
    const postPanel = document.querySelector(".status-container");
    const loginPanel = document.querySelector(".login-form");
    if (postPanel || loginPanel) {
      Object.keys(this.loadedMods).forEach((modName) => {
        const settings = document.querySelector(".settings-modal div[label]:first-child");
        if (settings) {
          if (!settings.querySelector(".mod-settings")) {
            this.appendModSettings(settings);
          }
        }

        const mod = this.loadedMods[modName];
        if (mod.isEnabled) {
          mod.ready();
        }
      });

      this.createObserver();
    } else {
      console.warn("not ready, trying again in 1s");
      window.setTimeout(() => { this.waitUntilReady(); }, 1000);
    }
  }

  createCheckbox (label, mod) {
    const labelElement = document.createElement("label");
    labelElement.classList.add("checkbox");

    const input = document.createElement("input");
    input.setAttribute("type", "checkbox");
    input.checked = mod.isEnabled;
    input.addEventListener("change", (event) => {
      if (event.target.checked) {
        mod.enable();
      } else {
        mod.disable();
      }
    });
    labelElement.appendChild(input);

    const fakeCheckbox = document.createElement("i");
    fakeCheckbox.classList.add("checkbox-indicator");
    labelElement.appendChild(fakeCheckbox);

    const text = document.createElement("span");
    text.classList.add("label");
    text.innerText = label;
    labelElement.appendChild(text);

    return labelElement;
  }

  appendModSettings (element) {
    const container = document.createElement("div");
    container.classList.add("setting-item");
    container.classList.add("mod-settings");

    const title = document.createElement("h2");
    title.innerText = "Pleroma Mods";
    container.appendChild(title);

    const optionList = document.createElement("ul");
    optionList.classList.add("setting-list");

    Object.keys(this.loadedMods).sort().forEach((modName) => {
      const li = document.createElement("li");

      const enable = this.createCheckbox("enable " + modName, this.loadedMods[modName]);
      li.appendChild(enable);

      const ulConfig = document.createElement("ul");
      ulConfig.classList.add("setting-list");

      Object.keys(this.loadedMods[modName].config).forEach((key) => {
        if (key === "includes" || key === "filter") {
          return;
        }

        this.loadedMods[modName].onSettingInit(key, ulConfig, document.createElement("li"));
      });

      li.appendChild(ulConfig);

      optionList.appendChild(li);
    });

    container.appendChild(optionList);

    element.appendChild(container);
  }

  createObserver () {
    this.containers = {
      main: document.querySelector(".main"),
      notifications: document.querySelector(".notifications"),
      userPanel: document.querySelector(".user-panel"),
      settingsModal: document.querySelector(".settings-modal")
    };

    const observerConfig = {
      subtree: true,
      childList: true
    };

    this.observer = new MutationObserver((mutations, observer) => {
      const modal = document.querySelector(".settings-modal div[label]:first-child");
      if (modal && !modal.querySelector(".mod-settings")) {
        this.appendModSettings(modal);
      }

      Object.values(this.loadedMods).forEach((mod) => {
        if (mod.isEnabled) {
          mutations.forEach((mutation) => {
            mod.mutate(mutation, observer);
          });
        }
      });
    });

    this.observer.observe(this.containers.main, observerConfig);
    if (this.containers.notifications) {
      this.observer.observe(this.containers.notifications, observerConfig);
    }
    if (this.containers.userPanel) {
      this.observer.observe(this.containers.userPanel, observerConfig);
    }
    if (this.containers.settingsModal) {
      this.observer.observe(this.containers.settingsModal, observerConfig);
    }
  }

  static registerMod (mod) {
    window.__pleromaModLoader.registerClass(mod.name, mod);
  }

  static includeScript (src) {
    console.log("include " + src);
    return new Promise((resolve) => {
      const script = document.createElement("script");
      script.setAttribute("src", src);
      script.setAttribute("type", "text/javascript");
      script.onload = () => {
        resolve();
      };
      document.querySelector("body").appendChild(script);
    });
  }

  static includeCss (src) {
    console.log("include " + src);
    return new Promise((resolve) => {
      const link = document.createElement("link");
      link.setAttribute("href", src);
      link.setAttribute("rel", "stylesheet");
      link.setAttribute("type", "text/css");
      link.onload = () => {
        resolve();
      };
      document.querySelector("head").appendChild(link);
    });
  }

  static excludeScript (src) {
    return new Promise((resolve) => {
      const script = document.querySelector("script[src=\"" + src + "\"]");
      if (script) {
        script.remove();
      }
      resolve();
    });
  }

  static excludeCss (src) {
    return new Promise((resolve) => {
      const link = document.querySelector("link[href=\"" + src + "\"]");
      if (link) {
        link.remove();
      }
      resolve();
    });
  }

  static getVueScope (element) {
    if (!element) {
      return null;
    }
    if (element.__vue__) {
      console.warn("old vue version, please update pleroma-fe");
      return element.__vue__;
    }
    if (element._vnode) {
      return element._vnode;
    }
    if (element.__vnode) {
      return element.__vnode;
    }
    if (element.parentNode) {
      return PleromaModLoader.getVueScope(element.parentNode);
    }
    return null;
  }

  static getVueComponent (element) {
    if (!element) {
      return null;
    }
    if (element.__vnode && element.__vnode.component) {
      return element.__vnode.component;
    }
    if (element.__vueParentComponent) {
      return element.__vueParentComponent.ctx;
    }
    if (element.__vueComponent__) {
      return element.__vueComponent__;
    }
    if (element.parentNode) {
      return PleromaModLoader.getVueComponent(element.parentNode);
    }
    return null;
  }

  static getRootVueScope () {
    return PleromaModLoader.getVueScope(document.querySelector("#app"));
  }

  static getToken () {
    return PleromaModLoader.getRootVueScope().appContext.provides.store.getters.getUserToken();
  }

  static getModDir () {
    return window.__pleromaModLoader.config.modDirectory;
  }

  static getClassName (name) {
    let className = "PleromaMod";
    name.split("-").forEach((namePart) => {
      className += namePart.substring(0, 1).toUpperCase();
      className += namePart.substring(1);
    });
    return className;
  }

  static api (method, path, params) {
    return new Promise((resolve, reject) => {
      const token = PleromaModLoader.getToken();
      const xhr = new XMLHttpRequest();
      xhr.responseType = "json";
      xhr.open(method, path);
      xhr.setRequestHeader("Content-Type", "application/json");
      xhr.setRequestHeader("Authorization", "Bearer " + token);
      xhr.onreadstatechange = () => {
        if (xhr.readyState === 4) {
          if (xhr.status !== 200) {
            reject(new Error({
              status: xhr.status,
              response: xhr.response,
              xhr: xhr
            }));
          }
        }
      };
      xhr.onload = () => {
        resolve(xhr.response);
      };
      xhr.send(JSON.stringify(params));
    });
  }
}

class PleromaMod { // eslint-disable-line no-unused-vars
  constructor (name) {
    this.name = name;
    this.config = {};
    this.isRunning = false;
  }

  get isEnabled () {
    return localStorage.getItem("pleroma_mod_" + this.name + "_enabled") !== "false" || true;
  }

  getClassName () {
    return PleromaModLoader.getClassName(this.name);
  }

  getModDir () {
    return PleromaModLoader.getModDir() + this.name + "/";
  }

  ready () {
    this.onReady();
    this.run();
  }

  destroy () {
    this.isRunning = false;
    if (this.config.includes) {
      const styles = this.config.includes.css || [];
      const scripts = this.config.includes.js || [];

      Promise.all(styles.map((style) => {
        return this.excludeCss(style);
      }).concat(scripts.map((script) => {
        return this.excludeScript(script);
      }))).then(() => {
        this.onDestroy();
      });

      return;
    }

    this.onDestroy();
  }

  run () {
    if (this.config.includes) {
      const styles = this.config.includes.css || [];
      const scripts = this.config.includes.js || [];

      Promise.all(styles.map((style) => {
        return this.includeCss(style);
      }).concat(scripts.map((script) => {
        return this.includeScript(script);
      })).concat([
        this.loadConfig(),
        this.onCreate()
      ])).then(() => {
        this.isRunning = true;
        this.onRun();
      });

      return;
    }

    this.isRunning = true;
    this.onRun();
  }

  mutate (mutation, observer) {
    if (this.isRunning) {
      this.onMutation(mutation, observer);
    }
  }

  saveConfig () {
    const storedConfig = {};
    Object.keys(this.config).filter((key) => {
      return key !== "includes" && key !== "filter";
    }).forEach((key) => {
      storedConfig[key] = this.config[key];
    });
    localStorage.setItem(this.name + "_config", JSON.stringify(storedConfig));
  }

  mergeConfig (newConfig) {
    Object.keys(newConfig).forEach((key) => {
      this.config[key] = JSON.parse(JSON.stringify(newConfig[key]));
    });
  }

  loadConfig () {
    return new Promise((resolve) => {
      // TODO: use structuredClone when its more supported
      this.defaultConfig = JSON.parse(JSON.stringify(this.config));

      const storedConfig = JSON.parse(localStorage.getItem(this.name + "_config"));

      this.onConfigLoad().then((json) => {
        this.mergeConfig(json);
        if (storedConfig) {
          this.mergeConfig(storedConfig);
        }
        this.saveConfig();
        resolve();
      });
    });
  }

  onReady () {}
  onCreate () {
    return new Promise((resolve) => {
      resolve();
    });
  }
  onDestroy () {}
  onRun () {}
  onMutation (mutation, observer) {}
  onConfigLoad () {
    return new Promise((resolve) => {
      resolve({});
    });
  }
  onSettingInit (key, ul, li) {}

  includeCss (src) {
    return PleromaModLoader.includeCss(PleromaModLoader.getModDir() + this.name + "/" + src);
  }

  includeScript (src) {
    return PleromaModLoader.includeScript(PleromaModLoader.getModDir() + this.name + "/" + src);
  }

  excludeCss (src) {
    return PleromaModLoader.excludeCss(PleromaModLoader.getModDir() + this.name + "/" + src);
  }

  excludeScript (src) {
    return PleromaModLoader.excludeScript(PleromaModLoader.getModDir() + this.name + "/" + src);
  }

  fetchJson (src) {
    console.log("loading " + src);
    return window.fetch(PleromaModLoader.getModDir() + this.name + "/" + src).then((response) => {
      return response.json();
    });
  }

  api (method, path, params) {
    return PleromaModLoader.api(method, path, params);
  }

  enable () {
    this.ready();
    localStorage.setItem("pleroma_mod_" + this.name + "_enabled", true);
  }

  disable () {
    this.destroy();
    localStorage.setItem("pleroma_mod_" + this.name + "_enabled", false);
  }
}

window.__pleromaModLoader = new PleromaModLoader();
window.__pleromaModLoader.waitUntilReady();
