/**
 * The settings API is meant .
 * @module Settings
 * @memberof API
 * @namespace API.Settings
 * @version 1.0.0
 */

/**
 * @typedef SettingsCategory
 * @property {Function} connectStore Connects a component to the settings store
 * @property {function(string, string): string} get Gets a setting, or fallbacks to default value
 * @property {function(): string[]} getKeys Get all settings key
 * @property {function(string): void} delete Deletes a setting
 * @property {function(string, string): void} set Sets a setting
 */

/**
 * @typedef SettingsTab
 * @property {string} category Settings category. Most of the time, you want this to be the entity ID
 * @property {string|function(): string} label Settings tab label
 * @property {function(): React.ReactNode} render Render method
 * @property {undefined} settings Use it and you'll be fined 69 cookies
 */

import { Sidebar, Content, Layout } from '@vizality/components/dashboard';
import { toPlural } from '@vizality/util/string';
import { getCaller } from '@vizality/util/file';
import { Events } from '@vizality/constants';
import { Flux } from '@vizality/webpack';
import { API } from '@vizality/entities';
import React, { useState } from 'react';

import actions from './store/Actions';
import store from './store/Store';

/**
 * @extends API
 * @extends Events
 */
export default class Settings extends API {
  constructor () {
    super();
    /**
     * @property {Flux.Store} store Flux store
     */
    this.store = store;
  }

  /**
   * Shuts down the API, removing all listeners and stored objects.
   */
  stop () {
    this.removeAllListeners();
    delete vizality.api.settings;
  }

  /**
   * Builds a settings category that can be used by a plugin.
   * @private
   * @param {string} category Settings category name
   * @returns {SettingsCategory}
   */
  _buildCategoryObject (category) {
    return {
      connectStore: component => this.connectStores(category)(component),
      getKeys: () => store.getSettingsKeys(category),
      get: (setting, defaultValue) => store.getSetting(category, setting, defaultValue),
      toggle: (setting, defaultValue) => {
        return actions.toggleSetting(category, setting, defaultValue);
      },
      set: (setting, newValue) => {
        actions.updateSetting(category, setting, newValue);
      },
      delete: (setting) => {
        actions.deleteSetting(category, setting);
      }
    };
  }

  /**
   * Creates a flux decorator for a given settings category.
   * @param {string} category Settings category
   * @returns {Function}
   */
  connectStores (category) {
    return Flux.connectStores([ this.store ], () => this._fluxProps(category));
  }

  /**
   * Registers a settings tab.
   * @private
   * @param {SettingsTab} props Props of the settings tab
   */
  registerSettings (props) {
    try {
      let { type, addonId, render } = props;

      type = type || 'plugin';

      render =
        render?.__esModule
          ? render?.default
          : render?.type
            ? render.type
            : render;

      if (!render) {
        throw new Error(`You must specify a render component to register settings for "${addonId}"!`);
      }

      const addon = vizality.manager[toPlural(type)].get(addonId);

      if (!addon) {
        throw new Error(`Cannot register settings for "${addonId}" because it isn't installed!`);
      }

      addon.sections.settings = {
        component: render,
        render: this.connectStores(addonId)(render)
      };

      const Render = addon.sections.settings.render;

      vizality.api.routes.registerRoute({
        id: `${type}/${addonId}`,
        path: `/${toPlural(type)}/${addonId}`,
        render: props =>
          <Layout>
            <Content
              header='Settings'
              vz-plugin={Boolean(type === 'plugin') && addonId}
              vz-theme={Boolean(type === 'theme') && addonId}
              vz-plugin-section={Boolean(type === 'plugin') && 'settings'}
              vz-theme-section={Boolean(type === 'theme') && 'settings'}
            >
              <Render {...props} />
            </Content>
          </Layout>,
        sidebar: Sidebar
      });

      this.emit(Events.VIZALITY_SETTINGS_REGISTER, addonId);
    } catch (err) {
      return this.error(err);
    }
  }

  /**
   * Registers a settings tab.
   * @param {SettingsTab} settings Props of the settings tab
   * @private
   */
  /*
   * registerSettings (settings) {
   *   try {
   *     let { type, addonId, render } = settings;
   */

  //     type = type || 'plugins';

  /*
   *     render =
   *       render?.__esModule
   *         ? render?.default
   *         : render?.type
   *           ? render.type
   *           : render;
   */

  /*
   *     if (!render) {
   *       throw new Error(`You must specify a render component to register settings for "${addonId}"!`);
   *     }
   */

  //     const addon = vizality.manager[type].get(addonId);

  /*
   *     if (!addon) {
   *       throw new Error(`Cannot register settings for "${addonId}" because it isn't installed!`);
   *     }
   */

  /*
   *     addon.sections.settings = {
   *       component: render,
   *       render: this.connectStores(addonId)(render)
   *     };
   */

  /*
   *     this.emit(Events.VIZALITY_SETTINGS_REGISTER, addonId);
   *   } catch (err) {
   *     return this.error(err);
   *   }
   * }
   */

  /**
   * Unregisters a settings tab.
   * @private
   * @param {string} addonId Addon ID of the settings to unregister
   * @param {string} type Type of the addon
   */
  unregisterSettings (addonId, type) {
    try {
      const addon = vizality.manager[toPlural(type)].get(addonId);
      if (addon?.sections?.settings) {
        delete addon.sections.settings;
      } else {
        throw new Error(`Settings for "${addonId}" are not registered, so they cannot be unregistered!`);
      }
      vizality.api.routes.unregisterRoute(`/${toPlural(type)}/${addonId}`);
      this.emit(Events.VIZALITY_SETTINGS_UNREGISTER, addonId);
    } catch (err) {
      return this.error(err);
    }
  }

  /**
   * @private
   * @param {...any} props
   */
  _registerBuiltinSection (props) {
    try {
      const addonId = getCaller()?.id;
      const { header, description, icon, render } = props;
      const builtin = vizality.manager.builtins.get(addonId);
      builtin.sections.settings = props;
      builtin.sections.settings.render = this.connectStores(addonId)(render);
      const Render = builtin.sections.settings.render;
      vizality.api.routes.registerRoute({
        id: addonId,
        path: `/${addonId}`,
        render: props =>
          <Layout>
            <Content
              header={header}
              description={description}
              icon={icon}
              vz-builtin={addonId}
            >
              <Render {...props} />
            </Content>
          </Layout>,
        sidebar: Sidebar
      });
    } catch (err) {
      return this.error(err);
    }
  }

  /**
   * A hook that allows you to easily update settings values using states, automatically
   * rerendering your component for you.
   * @param {string} settingKey Setting key
   * @param {any} defaultValue Default setting value
   * @example
   * ```
   * import { useSetting } from '@vizality/settings';
   *
   * export default memo(() => {
   *   const [ text, setText ] = useSetting('coolText', 'I like pie');
   *   return (
   *    {text}
   *    <Button onClick={() => setText('I like cake')} />
   *   )
   * )};
   * ```
   */
  useSetting (settingKey, defaultValue, addonId) {
    try {
      const settings = this._fluxProps(addonId);

      /**
       * If it doesn't find any settings, just return.
       */
      if (!settings) {
        return;
      }

      const [ settingValue, setSettingValue ] = useState(settings.getSetting(settingKey, defaultValue));
      /**
       * Updates the setting value.
       * @param {any} newValue Updated setting value
       * @returns {void}
       */
      const setSetting = newValue => {
        settings.updateSetting(settingKey, newValue);
        setSettingValue(newValue);
      };
      return [ settingValue, setSetting ];
    } catch (err) {
      return this.error(err);
    }
  }

  /**
   *
   * @private
   * @param {string} [addonId] Addon ID
   */
  _fluxProps (addonId) {
    /**
     * If no addonId is provided, try to use getCaller to determine one.
     */
    addonId = addonId || getCaller()?.id;
    /**
     * If the addonId is vizality, it was most likely returned from getCaller
     * and is used in some core area, so we want to use the core Vizality settings.json
     * file for its store.
     */
    if (addonId === 'vizality') {
      addonId = 'settings';
    }
    return {
      /**
       *
       */
      settings: store.getSettings(addonId),

      /**
       *
       * @param {*} setting
       * @param {*} defaultValue
       */
      getSetting: (setting, defaultValue) => {
        return store.getSetting(addonId, setting, defaultValue);
      },

      /**
       *
       * @param {*} setting
       * @param {*} value
       */
      updateSetting: (setting, value) => {
        if (addonId === 'settings') {
          this.emit(Events.VIZALITY_SETTING_UPDATE, setting, value);
        } else {
          this.emit(Events.VIZALITY_ADDON_SETTING_UPDATE, addonId, setting, value);
        }
        return actions.updateSetting(addonId, setting, value);
      },

      /**
       *
       * @param {*} setting
       * @param {*} defaultValue
       */
      toggleSetting: (setting, defaultValue) => {
        if (addonId === 'settings') {
          this.emit(Events.VIZALITY_SETTING_TOGGLE, setting, defaultValue);
        } else {
          this.emit(Events.VIZALITY_ADDON_SETTING_TOGGLE, addonId, setting, defaultValue);
        }
        return actions.toggleSetting(addonId, setting, defaultValue);
      }
    };
  }
}
