import { toPlural, toKebabCase, toTitleCase } from '@vizality/util/string';
import { log, warn, error } from '@vizality/util/logger';
import { resolveCompiler } from '@vizality/compilers';
import { createElement } from '@vizality/util/dom';
import { jsonToReact } from '@vizality/util/react';
import { Directories } from '@vizality/constants';
import { isArray } from '@vizality/util/array';
import { debounce } from 'lodash';

import Updatable from './Updatable';

/**
 * @extends Updatable
 * @extends Events
 */
export default class Theme extends Updatable {
  constructor (addonId, manifest) {
    super(Directories.THEMES, addonId);
    this.settings = vizality.api.settings._buildCategoryObject(this.addonId);
    this.compiler = resolveCompiler(manifest.effectiveTheme);
    this.manifest = manifest;
    this.sections = {};
    this.type = 'theme';
    this._applied = false;
    this._labels = [ 'Theme', this.manifest?.name ];
  }

  /**
   * 
   * @param {*} render 
   */
  registerSettings (render) {
    vizality.api.settings.registerSettings({
      type: this.type,
      addonId: this.addonId,
      render
    });
  }

  /**
   * 
   * @param  {...any} message 
   */
  log (...message) {
    // In case the addon wants to provide their own labels
    if (isArray(message[0])) {
      const _message = message.slice(1);
      log({ labels: message[0], message: _message });
    } else {
      log({ labels: this._labels, message });
    }
  }

  /**
   * 
   * @param  {...any} message 
   */
  warn (...message) {
    // In case the addon wants to provide their own labels
    if (isArray(message[0])) {
      const _message = message.slice(1);
      warn({ labels: message[0], message: _message });
    } else {
      warn({ labels: this._labels, message });
    }
  }

  /**
   * 
   * @param  {...any} message 
   */
  error (...message) {
    // In case the addon wants to provide their own labels
    if (isArray(message[0])) {
      const _message = message.slice(1);
      error({ labels: message[0], message: _message });
    } else {
      error({ labels: this._labels, message });
    }
  }

  /**
   * 
   * @private
   */
  _load () {
    if (!this._applied) {
      this._applied = true;
      const style = createElement('style', {
        id: `${this.type}-${this.addonId}`,
        'vz-style': true,
        [`vz-${this.type}`]: true
      });
      document.head.appendChild(style);
      this._doCompilation = debounce(async (showLogs = true) => {
        try {
          const before = performance.now();
          const css = await this.compiler.compile();
          style.innerHTML = css;
          if (showLogs) {
            const after = performance.now();
            const time = parseFloat((after - before).toFixed()).toString().replace(/^0+/, '') || 0;
            /**
             * Let's format the milliseconds to seconds.
             */
            let formattedTime = Math.round((time / 1000 + Number.EPSILON) * 100) / 100;
            /**
             * If it ends up being so fast that it rounds to 0, let's show formatting
             * to 3 decimal places, otherwise show 2 decimal places.
             */
            if (formattedTime === 0) {
              formattedTime = Math.round((time / 1000 + Number.EPSILON) * 1000) / 1000;
            }
            /**
             * If it is still 0, let's just say it's fast.
             */
            if (formattedTime === 0) {
              return this.log(`${toTitleCase(this.type)} compiled. Compilation was nearly instant!`);
            }
            return this.log(`${toTitleCase(this.type)} compiled. Compilation took ${formattedTime} seconds!`);
          }
        } catch (err) {
          return this.error('There was a problem compiling!', err);
        }
      }, 300);
      this.compiler.enableWatcher();
      this.compiler.on('src-update', this._doCompilation);
      this.log(`${toTitleCase(this.type)} loaded.`);
      if (Array.isArray(this.manifest.settings)) {
        const settings = this._mapSettings(this.manifest.settings);
        this.registerSettings(() => jsonToReact(settings, (id, value) => {
          this.settings.set(id, value);
          this._injectSettings();
        }));
        this._injectSettings();
      }
      return this._doCompilation(false);
    }
  }

  /**
   * 
   * @private
   * @param {*} settings 
   * @returns 
   */
  _mapSettings (settings) {
    return settings.map(setting => {
      if (setting.type === 'category') {
        return {
          ...setting,
          items: this._mapSettings(setting.items)
        };
      }
      if (setting.type === 'divider' ||
          setting.type === 'markdown') {
        return setting;
      }
      return {
        ...setting,
        get value () { return this.settings.get(setting.id, setting.defaultValue); },
        settings: this.settings
      };
    });
  }

  /**
   * 
   * @private
   * @param {*} _settings 
   * @returns 
   */
  _getSettings (_settings) {
    const settings = [];
    for (const setting of _settings) {
      if (setting.type === 'category') {
        settings.push(...this._getSettings(setting.items));
        continue;
      }
      if (setting.type === 'divider' || setting.type === 'markdown') {
        continue;
      }
      settings.push({
        type: setting.type,
        id: toKebabCase(setting.id),
        value: this.settings.get(setting.id, setting.defaultValue)
      });
    }
    return settings;
  }

  /**
   * 
   * @private
   */
  _injectSettings () {
    if (!this.settingsStyle) {
      this.settingsStyle = document.head.appendChild(createElement('style', {
        id: `theme_settings-${this.addonId}`,
        'vz-theme': true,
        'vz-style': true
      }));
    }
    let settingsString = '\n:root {';
    const settingsItems = this._getSettings(this.manifest.settings);
    for (const setting of settingsItems) {
      settingsString += `\n\t--${setting.id}: ${typeof setting.value === 'string' && setting.type !== 'color'
        ? `"${setting.value}"`
        : typeof setting.value === 'number'
          ? `${setting.value}px`
          : setting.value
      } !important;`;
    }
    this.settingsStyle.innerHTML = settingsString += '\n}\n';
  }

  /**
   * 
   * @private
   */
  _destroySettings () {
    if (this.settingsStyle) {
      this.settingsStyle.remove();
    }
    if (this.sections?.settings) {
      vizality.api.settings.unregisterSettings(this.addonId, 'theme');
    }
  }

  /**
   * Unloads a theme.
   * @private
   */
  _unload () {
    try {
      if (this._applied) {
        this._applied = false;
        this.compiler.off('src-update', this._doCompilation);
        document.getElementById(`${this.type}-${this.addonId}`)?.remove();
        this.compiler.disableWatcher();
        this._destroySettings();
        this.log(`${toTitleCase(this.type)} unloaded!`);
      }
    } catch (err) {
      this.error(`An error occurred while shutting down! It's heavily recommended that you reload Discord to ensure there are no conflicts.`, err);
    }
  }
}
