/* eslint-disable no-unused-vars */
/**
 * Contains functions that output stylized messages to developer tools console.
 * @module Logger
 * @memberof Util
 * @namespace Util.Logger
 */

import { getRandomColor, getContrastColor, blendColors, shadeColor } from './Color';
import { isArray, isEmptyArray, assertArray } from './Array';
import { isString, assertString } from './String';
import { assertObject } from './Object';

const _labels = [ 'Util', 'Logger' ];

/**
 * Sets the maximum amount of badges that can be used in a single console message.
 */
export const MAX_LABELS_COUNT = 10;

/**
 * Contains a list of modules and their color badge associations.
 */
export const PRESET_LABELS = {
  watcher:        [ '#FFC600' ],
  vizality:       [ '#121321', '#ffffff' ],
  compiler:       [ '#38964e', '#c01b91' ],
  api:            [ '#ff006a', '#414875' ],
  util:           [ '#4b2d73', '#c65d00' ],
  http:           [ '#e63200', '#2e89c9' ],
  manager:        [ '#782049', '#6f9150' ],
  builtin:        [ '#267366', '#b63754' ],
  plugin:         [ '#42ffa7', '#594bda' ],
  theme:          [ '#b68aff', '#f3523d' ],
  discordnative:  [ '#7289da', '#18191c' ],
  hooks:          [ '#ed7c6f', '#34426e' ],
  i18n:           [ '#a70338', '#0195b5' ],
  component:      [ '#162a2e', '#e58ede' ],
  discord:        [ '#b68aff', '#f3523d' ],
  constants:      [ '#b68aff', '#f3523d' ],
  classes:        [ '#b68aff', '#f3523d' ]
};

/**
 * Logs an informational message to dev tools console.
 * @param {object} options Options for the console message
 * @param {string} [options.type='log'] Type of console method to use
 * @param {string} [options.badge='vizality://assets/images/logo.png'] Badge image URL
 * @param {Array<string|object>} [options.labels=[]] Label texts or label objects. Limit of 10.
 * @param {string} [options.labels.text] Label text
 * @param {string} [options.labels.color] Label color
 * @param {any} [options.message] Message to log
 */
export function log (options) {
  try {
    assertObject(options);
    options.type = 'log';
    return _handler(options);
  } catch (err) {
    return _error(_labels.concat('log'), err);
  }
}

/**
 * Logs a warning message to dev tools console.
 * @param {object} options Options for the console message
 * @param {string} [options.type='log'] Type of console method to use
 * @param {string} [options.badge='vizality://assets/images/logo.png'] Badge image URL
 * @param {Array<string|object>} [options.labels=[]] Label texts or label objects. Limit of 10.
 * @param {string} [options.labels.text] Label text
 * @param {string} [options.labels.color] Label color
 * @param {any} [options.message] Message to log
 */
export function warn (options) {
  try {
    assertObject(options);
    options.type = 'warn';
    return _handler(options);
  } catch (err) {
    return _error(_labels.concat('warn'), err);
  }
}

/**
 * Logs an error message to dev tools console.
 * @param {object} options Options for the console message
 * @param {string} [options.type='log'] Type of console method to use
 * @param {string} [options.badge='vizality://assets/images/logo.png'] Badge image URL
 * @param {Array<string|object>} [options.labels=[]] Label texts or label objects. Limit of 10.
 * @param {string} [options.labels.text] Label text
 * @param {string} [options.labels.color] Label color
 * @param {any} [options.message] Message to log
 */
export function error (options) {
  try {
    assertObject(options);
    options.type = 'error';
    return _handler(options);
  } catch (err) {
    return _error(_labels.concat('error'), err);
  }
}

/**
 * Logs a deprecation (warning) message to dev tools console.
 * @param {object} options Options for the console message
 * @param {string} [options.type='log'] Type of console method to use
 * @param {string} [options.badge='vizality://assets/images/logo.png'] Badge image URL
 * @param {Array<string|object>} [options.labels=[]] Label texts or label objects. Limit of 10.
 * @param {string} [options.labels.text] Label text
 * @param {string} [options.labels.color] Label color
 * @param {any} [options.message] Message to log
 */
export function deprecate (options) {
  try {
    assertObject(options);
    const { message } = options;
    options.type = 'warn';
    options.message = `Deprecation Notice: ${message}`;
    return _handler(options);
  } catch (err) {
    return _error(_labels.concat('deprecate'), err);
  }
}

/**
 * Sends a stylized error message to console tuned to this file.
 * @param {Array<string>} labels Label text
 * @param  {...any} message Message to log
 * @returns {void}
 * @private
 */
function _error (labels, ...message) {
  return error({ labels, message });
}

/**
 * Processes which type of console method to use.
 * @param {any} type Type of console method to use
 * @returns {('log'|'warn'|'error')}
 * @private
 */
function _parseType (type) {
  return [ 'log', 'warn', 'error' ].find(t => t === type) || 'log';
}

/**
 * Outputs messages to console of varying types. Outputted messages contain a badge, label(s), and a message.
 * @param {object} options Options for the console message
 * @param {string} [options.type='log'] Type of console method to use
 * @param {string} [options.badge='vizality://assets/images/logo.png'] Badge image URL
 * @param {Array<string|object>} [options.labels=[]] Label texts or label objects. Limit of 10.
 * @param {string} [options.labels.text] Label text
 * @param {string} [options.labels.color] Label color
 * @param {any} [options.message] Message to log
 * @private
 */
function _handler (options) {
  try {
    assertObject(options);
    let { type, badge, labels, message } = options;

    /**
     * Set up fallbacks for some options.
     */
    labels = labels || [];
    type = _parseType(type);
    badge = badge || 'vizality://assets/images/logo.png';

    /**
     * Throw an error if any of the arg types aren't as expected.
     */
    assertArray(labels);
    assertString(type);
    assertString(badge);

    /**
     * If message isn't an array, turn it into one so we can process them all the same.
     */
    if (!isArray(message)) message = [ message ];

    const baseBadgeStyles = `
      display: inline-block;
      text-align: center;
      border-radius: 2px;
      font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
      text-transform: uppercase;
      font-size: 10px;
      font-weight: 600;
      line-height: 14px;
      margin-right: 3px;
      padding: 1px 4px;`;

    const badgeStyles = `
      display: inline-block;
      background-image: url('${badge}');
      background-repeat: no-repeat;
      background-position: center;
      background-size: contain;
      border-radius: 2px;
      text-align: center;
      padding: 2px 14px 5px 0;
      margin: 0 4px 1px 0;`;

    /**
     * If there aren't any labels, just send the badge and message.
     */
    if (!labels || isEmptyArray(labels)) {
      return console[type](
        `%c `,
        badgeStyles,
        ...message
      );
    }

    const processedLabels = [];
    for (const [ index, label ] of labels.entries()) {
      /**
       *
       */
      if (isString(label)) {
        let color;
        if ((index === 0 || index === 1) && PRESET_LABELS[labels[0].toLowerCase()]) {
          color = PRESET_LABELS[labels[0].toLowerCase()][index];
        } else if (index === 2 && PRESET_LABELS[labels[0].toLowerCase()]) {
          color = shadeColor(blendColors(PRESET_LABELS[labels[0].toLowerCase()][0], processedLabels[index - 1]?.color, 0.5), -0.5);
        } else if (index > 2 && PRESET_LABELS[labels[0].toLowerCase()]) {
          color = shadeColor(processedLabels[index - 1]?.color, 0.2);
        } else {
          color = getRandomColor();
        }
        processedLabels.push({
          text: label,
          color
        });
      /**
       *
       */
      } else {
        processedLabels.push({
          text: label.text,
          color: label.color || getRandomColor()
        });
      }
    }

    const texts = [];
    const styles = [];

    /**
     *
     */
    for (const label of processedLabels.slice(0, MAX_LABELS_COUNT)) {
      if (!label?.text || !label?.color) {
        throw new Error('Each label must contain a valid text and color property.');
      }
      texts.push(`%c${label.text}`);
      styles.push(
        `${baseBadgeStyles};
        color: ${getContrastColor(label.color)};
        background: ${label.color};`
      );
    }

    /**
     *
     */
    return console[type](
      `%c ${texts.join('')}`,
      badgeStyles,
      ...styles,
      ...message
    );
  } catch (err) {
    return _error(_labels.concat('_handler'), err);
  }
}
