/* global L, log -- eslint */

/**
 * @file This file contains functions related to the 'About IITC' dialog.
 * @module dialog_about
 */

/**
 * Displays the 'About IITC' dialog.
 * This dialog includes the IITC version, a list of loaded plugins, and other relevant information.
 *
 * @function
 */
window.aboutIITC = function () {
  var html = createDialogContent();

  window.dialog({
    title: 'IITC ' + getIITCVersion(),
    id: 'iitc-about',
    html: html,
    width: 'auto',
    dialogClass: 'ui-dialog-aboutIITC',
  });
};

/**
 * Creates the content for the 'About IITC' dialog.
 *
 * @function
 * @returns {string} HTML content for the about dialog.
 */
function createDialogContent() {
  var html = `<div><b>About IITC</b></div>
              <div>Ingress Intel Total Conversion</div>
              <hr>
              <div>
               <a href="${'@url_homepage@'}" target="_blank">IITC Homepage</a> |
               <a href="${'@url_tg@'}" target="_blank">Telegram channel</a><br />
               On the script’s homepage you can:
               <ul>
                 <li>Find Updates</li>
                 <li>Get Plugins</li>
                 <li>Report Bugs</li>
                 <li>Contribute!</li>
               </ul>
              </div>
              <hr>
              <div>Version: ${getIITCVersion()} ${createChangelog(window.script_info)}</div>`;

  if (isShortOnLocalStorage()) {
    html += '<div class="warning">You are running low on LocalStorage memory.<br/>Please free some space to prevent data loss.</div>';
  }

  if (window.isApp && window.app.getVersionName) {
    html += '<div>IITC Mobile ' + window.app.getVersionName() + '</div>';
  }

  var plugins = getPlugins();
  if (plugins) {
    html += '<div><p>Plugins:</p><ul>' + plugins + '</ul></div>';
  }

  return html;
}

/**
 * Retrieves a list of plugins currently loaded in IITC. The list includes plugin names, versions, and descriptions.
 *
 * @function
 * @returns {string} Formatted list of plugins in HTML.
 */
function getPlugins() {
  var pluginsInfo = window.bootPlugins.info;

  while (window.bootPlugins[0]) {
    var plugin = window.bootPlugins.shift();
    pluginsInfo.push(plugin.info || {});
  }

  var extra = getIITCVersionAddition();

  var plugins = pluginsInfo
    .map(convertPluginInfo)
    .sort(function (a, b) {
      return a.name > b.name ? 1 : -1;
    })
    .map(function (p) {
      return pluginInfoToString(p, extra);
    })
    .join('\n');

  return plugins;
}

/**
 * Converts plugin information into a structured object for easier processing.
 *
 * @function
 * @param {Object} info - The plugin information object.
 * @param {number} index - The index of the plugin in the array.
 * @returns {Object} Structured plugin information.
 */
function convertPluginInfo(info, index) {
  // Plugins metadata come from 2 sources:
  // - buildName, pluginId, dateTimeVersion: inserted in plugin body by build script
  //   (only standard plugins)
  // - script.name/version/description: from GM_info object, passed to wrapper
  //   `script` may be not available if userscript manager does not provede GM_info
  //   (atm: IITC-Mobile for iOS)
  var result = {
    build: info.buildName,
    changelog: info.changelog,
    id: info.pluginId,
    name: info.pluginId,
    date: info.dateTimeVersion,
    error: info.error,
    version: undefined,
    description: undefined,
  };

  var script = info.script;
  if (script) {
    if (typeof script.name === 'string') {
      result.name = script.name.replace(/^IITC[\s-]+plugin:\s+/i, ''); // cut non-informative name part
    }
    result.version = script.version;
    result.description = script.description;
  }

  if (!result.name) {
    if (window.script_info.script) {
      // check if GM_info is available
      result.name = '[unknown plugin: index ' + index + ']';
      result.description = "this plugin does not have proper wrapper; report to it's author";
    } else {
      // userscript manager fault
      result.name = '[3rd-party plugin: index ' + index + ']';
    }
  }

  return result;
}

/**
 * Creates a changelog section for a given plugin.
 *
 * @function
 * @param {Object} plugin - The plugin for which to create the changelog.
 * @returns {string} HTML string representing the changelog.
 */
function createChangelog(plugin) {
  var id = 'plugin-changelog-' + plugin.id;
  return (
    `<a onclick="$('#${id}').toggle()">changelog</a>` +
    `<ul id="${id}" style="display: none;">` +
    plugin.changelog
      .map(function (logEntry) {
        return (
          '<li>' +
          logEntry.version +
          '<ul>' +
          logEntry.changes
            .map(function (change) {
              return `<li>${change}</li>`;
            })
            .join('') +
          '</ul></li>'
        );
      })
      .join('') +
    '</ul>'
  );
}

/**
 * Converts plugin information into a string format suitable for display in the 'About IITC' dialog.
 *
 * @function
 * @param {Object} p - The plugin information object.
 * @param {string} extra - Additional version information.
 * @returns {string} Formatted plugin information string.
 */
function pluginInfoToString(p, extra) {
  var info = {
    changelog: '',
    class: '',
    description: p.description || '',
    name: p.name,
    verinfo: formatVerInfo(p, extra),
  };

  if (isStandardPlugin(p)) {
    info.class += 'plugin-is-standard';
  }

  if (p.error) {
    info.class += ' plugin-error';
    info.description = p.error;
  }

  if (p.changelog) {
    info.changelog = createChangelog(p);
  }

  return L.Util.template('<li class="{class}" title="{description}">{name}{verinfo} {changelog}</li>', info);
}

/**
 * Checks if a given plugin is a standard plugin based on the build name and date.
 * Standard plugins are those that match the build and date of the main IITC script.
 *
 * @function
 * @param {Object} plugin - The plugin object to check.
 * @returns {boolean} True if the plugin is standard, false otherwise.
 */
function isStandardPlugin(plugin) {
  return plugin.build === window.script_info.buildName && plugin.date === window.script_info.dateTimeVersion;
}

/**
 * Retrieves the IITC version string.
 *
 * @function
 * @returns {string} The IITC version string.
 */
function getIITCVersion() {
  var iitc = window.script_info;
  return ((iitc.script && iitc.script.version) || iitc.dateTimeVersion) + ' [' + iitc.buildName + ']';
}

/**
 * Extracts the additional version information from the IITC script version.
 *
 * @function
 * @returns {string} The additional version information, if any.
 */
function getIITCVersionAddition() {
  var extra = window.script_info.script && window.script_info.script.version.match(/^\d+\.\d+\.\d+(\..+)$/);
  return extra && extra[1];
}

/**
 * Formats version information for plugins and the main script.
 * If an 'extra' parameter is provided and matches the end of the version string, it is removed.
 * This is used to cut off a common timestamp appended to versions.
 * The function also prepares a tooltip showing the build number and date, if available.
 *
 * @function formatVerInfo
 * @param {Object} p - The plugin or script object containing version information.
 * @param {string} [extra] - An optional extra string to be removed from the version information.
 * @returns {string} Formatted version string with optional HTML tooltip.
 */
function formatVerInfo(p, extra) {
  if (p.version && extra) {
    var cutPos = p.version.length - extra.length;
    // cut extra version component (timestamp) if it is equal to main script's one
    if (p.version.substring(cutPos) === extra) {
      p.version = p.version.substring(0, cutPos);
    }
  }

  p.version = p.version || p.date;
  if (p.version) {
    var tooltip = [];
    if (p.build) {
      tooltip.push('[' + p.build + ']');
    }
    if (p.date && p.date !== p.version) {
      tooltip.push(p.date);
    }
    return L.Util.template(' - <code{title}>{version}</code>', {
      title: tooltip[0] ? ' title="' + tooltip.join(' ') + '"' : '',
      version: p.version,
    });
  }

  return '';
}

/**
 * Checks if the browser's local storage is running short on available space.
 * This function tries to write a specific amount of data to the local storage and captures any errors.
 * If an error occurs, it is an indication that the local storage has limited free space left.
 *
 * @function
 * @returns {boolean} Returns `true` if the local storage is running short on space, otherwise `false`.
 */
function isShortOnLocalStorage() {
  var MINIMUM_FREE_SPACE = 100000;
  try {
    localStorage.setItem('_MEM_CHECK_', '#'.repeat(MINIMUM_FREE_SPACE));
  } catch (e) {
    log.error('out of localstorage space', e);
    return true;
  }

  localStorage.removeItem('_MEM_CHECK_');
  return false;
}