/* This file is a part of @mdn/browser-compat-data
 * See LICENSE file for more information. */

import { styleText } from 'node:util';

import { compareVersions } from 'compare-versions';

import { createStatementGroupKey } from '../utils.js';
import compareStatements from '../../scripts/lib/compare-statements.js';

/** @import {Logger} from '../utils.js' */
/** @import {BrowserName, SimpleSupportStatement, SupportStatement} from '../../types/types.js' */

/**
 * Groups statements by group key.
 * @param {SimpleSupportStatement[]} data The support statements to group.
 * @returns {Map<string, SimpleSupportStatement[]>} the statement groups
 */
const groupByStatementKey = (data) => {
  /** @type {Map<string, SimpleSupportStatement[]>} */
  const groups = new Map();

  for (const support of data) {
    const key = createStatementGroupKey(support);
    const group = groups.get(key);
    if (group) {
      group.push(support);
    } else {
      groups.set(key, [support]);
    }
  }
  return groups;
};

/**
 * Formats a support statement as a simplified JSON-like version range.
 * @param {SimpleSupportStatement} support The statement to format
 * @returns {string} The formatted range
 */
const formatRange = (support) => {
  /** @type {string[]} */
  const result = [];
  if (support.version_added) {
    result.push(`added: ${support.version_added}`);
  }
  if (support.version_removed) {
    result.push(`removed: ${support.version_removed}`);
  }

  return `{ ${result.join(', ')} }`;
};

/**
 * Process data and check to make sure there aren't support statements whose version ranges overlap.
 * @param {SupportStatement} data The data to test
 * @param {BrowserName} browser The name of the browser
 * @param {object} options The check options
 * @param {Logger} [options.logger] The logger to output errors to
 * @param {boolean} [options.fix] Whether the statements should be fixed (if possible)
 * @returns {SupportStatement} the data (with fixes, if specified)
 */
export const checkOverlap = (data, browser, { logger, fix = false }) => {
  if (!Array.isArray(data)) {
    // If there's only one statement, skip since this is a linter for multiple statements
    return data;
  }

  const filteredData = data.filter((support) => !support.flags);

  const groups = groupByStatementKey(filteredData);

  for (const [groupKey, groupData] of groups.entries()) {
    const statements = groupData.slice().sort(compareStatements).reverse();

    for (let i = 0; i < statements.length - 1; i++) {
      const current = /** @type {SimpleSupportStatement} */ (statements.at(i));
      const next = /** @type {SimpleSupportStatement} */ (statements.at(i + 1));

      if (!statementsOverlap(current, next)) {
        continue;
      }

      let fixed = false;
      if (fix) {
        if (
          typeof current.version_removed === 'undefined' &&
          next.version_added !== false &&
          next.version_added !== 'preview'
        ) {
          current.version_removed = next.version_added;
          fixed = true;
        }
      }

      if (!fixed && logger) {
        logger.error(
          `${styleText('bold', browser)} statements overlap for ${styleText('bold', groupKey)}: ` +
            `[${formatRange(next)}, ${formatRange(current)}]`,
        );
      }
    }
  }

  return data;
};

/**
 * Checks if the support statements overlap in terms of their version ranges.
 * @param {SimpleSupportStatement} current the current statement.
 * @param {SimpleSupportStatement} next the chronologically following statement.
 * @returns {boolean} Whether the support statements overlap.
 */
const statementsOverlap = (current, next) => {
  if (typeof current.version_removed === 'string') {
    // If previous has no removed version, we always have an overlap.

    if (next.version_added === 'preview') {
      // Feature got re-introduced.
      return false;
    }

    if (
      typeof next.version_added === 'string' &&
      compareVersions(current.version_removed, next.version_added) <= 0
    ) {
      // No overlap.
      return false;
    }
  } else if (
    next.version_added === 'preview' &&
    current.partial_implementation === true
  ) {
    // Stable has partial support.
    // Preview has full support.
    return false;
  }

  return true;
};
