// CommunityScripts UI Library
// cs-ui-lib.js
(function () {
  // get base URL for graphQL queries
  const baseURL = document.querySelector("base")?.getAttribute("href") ?? "/";

  /**
   * This is a wrapped GraphQL (GQL) query caller
   * @param {...Object} reqData
   * @param {Object} reqData.query - GraphQL query
   * @param {Object}= reqData.variables - GraphQL variables
   * @returns {Object} - GQL response data with the `data` wrapper removed
   *
   * @example
   *   // fetch the count of organized scenes
   *   const filter = { organized: true };
   *   const query = `query findScenes($filter: SceneFilter) { findScenes(filter: $filter) { count } }`;
   *   const variables = { filter };
   *   const response = await callGQL({ query, variables });
   *   // response = { findScenes: { count: 3 } }
   */
  const callGQL = (reqData) =>
    fetch(`${baseURL}graphql`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(reqData),
    })
      .then((res) => res.json())
      .then((res) => res.data);

  /**
   * Get configuration of a plugin from the server via GraphQL
   * @param {string} pluginId - The ID of the plugin as it is registered in the server
   * @param {*}= fallback - Fallback value if the configuration is not found. Defaults to an empty object
   * @returns {Object} - The configuration object of the plugin as it is stored in the server
   *
   * @example
   *    // set default config
   *    const defaultConfig = { enabled: true, theme: 'light' };
   *    // fetch config from the server
   *    const config = await getConfiguration('CommunityScriptsUIPlugin', defaultConfig);
   *    // config = { theme: 'dark' }
   *    // merge fetched with default config
   *    const pluginConfig = {
   *      ...defaultConfig
   *      ...config
   *    };
   *    // pluginConfig = { enabled: true, theme: 'dark' }
   * }
   */
  const getConfiguration = async (pluginId, fallback = {}) => {
    const query = `query Configuration { configuration { plugins }}`;
    const response = await callGQL({ query });
    return response.configuration.plugins?.[pluginId] ?? fallback;
  };

  /**
   * Set configuration of a plugin in the server via GraphQL
   * @param {string} pluginId - The ID of the plugin as it is registered in the server
   * @param {*} values - The configuration object with the values you want to save in the server
   * @returns {Object} - The configuration object of the plugin as it is stored in the server after update
   *
   * @example
   *    // fetch config from the server
   *    const config = await getConfiguration('CommunityScriptsUIPlugin', defaultConfig);
   *    // config = { theme: 'dark' }
   *    // update the config based on user input
   *    // config = { theme: 'light' }
   *    // save config in the server
   *    await setConfiguration('CommunityScriptsUIPlugin', config);
   * }
   */
  const setConfiguration = async (pluginId, values) => {
    const query = `mutation ConfigurePlugin($pluginId: ID!, $input: Map!) { configurePlugin(plugin_id: $pluginId, input: $input) }`;
    const queryBody = {
      query: query,
      variables: {
        pluginId: pluginId,
        input: values,
      },
    };
    const response = await csLib.callGQL({ ...queryBody });
    return response.configurePlugin;
  };

  /**
   * Waits for an element to be available in the DOM and runs the callback function once it is
   * @param {string} selector - The CSS selector of the element to wait for
   * @param {function} callback - The function to be called once the element is available (with the element as an argument)
   * @returns
   *
   * @example
   *   // wait for the element with the class 'my-element' to be available
   *   // and change its color to red
   *   function myCallback(el) {
   *     el.style.color = 'red';
   *    };
   *    waitForElement('.my-element', myCallback);
   */
  function waitForElement(selector, callback) {
    var el = document.querySelector(selector);
    if (el) return callback(el);
    setTimeout(waitForElement, 100, selector, callback);
  }

  /**
   * Wait for a specific element to be available on a specific path
   * This combines the `waitForElement` and `PluginApi.Event.addEventListener` functions to only trigger on certain pages
   *
   * @param {string} path - The path to listen for
   * @param {string} element - The CSS selector of the element to wait for
   * @param {function} callback - The function to be called once the element is available (with the element as an argument)
   *
   * @example
   *  // wait for the element with the class 'my-element' to be available, but only on the `/scene/#` path
   *  function myCallback(el) {
   *    el.style.color = 'red';
   *  };
   *  PathElementListener('/scene/', '.my-element', myCallback);
   *  // this will only trigger the callback function when the user is on the `/scene/` path AND the element is available
   */
  const PathElementListener = (path, element, callback) => {
    // startup location
    if (window.location.pathname.startsWith(path))
      waitForElement(element, callback);
    PluginApi.Event.addEventListener("stash:location", (e) => {
      if (e.detail.data.location.pathname.startsWith(path))
        waitForElement(element, callback);
    });
  };

  // export to window
  window.csLib = {
    baseURL,
    callGQL,
    getConfiguration,
    setConfiguration,
    waitForElement,
    PathElementListener,
  };
})();
