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

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { compare } from 'compare-versions';
import esMain from 'es-main';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';

import {
  BrowserName,
  SimpleSupportStatement,
  CompatStatement,
} from '../types/types.js';
import bcd from '../index.js';
import { IS_WINDOWS } from '../test/utils.js';

const dirname = fileURLToPath(new URL('.', import.meta.url));

/**
 * Get the earliest version number from an array of versions
 *
 * @param {string[]} args The version numbers to check
 * @returns {string} The earliest of the version numbers
 */
const getEarliestVersion = (...args: string[]): string => {
  const versions = args
    .filter((version) => typeof version === 'string' && version !== 'preview')
    .map((version) => version.replace('≤', ''));

  let earliestVersion;

  for (const version of versions) {
    if (
      !earliestVersion ||
      earliestVersion === 'preview' ||
      (version !== 'preview' && compare(earliestVersion, version, '>'))
    ) {
      earliestVersion = version;
    }
  }

  return earliestVersion;
};

/**
 * Removes redundant flags from the compatibility data
 *
 * @param {string} key The object key (make sure it's '__compat')
 * @param {CompatStatement} value The compatibility statement to test
 * @param {BrowserName?} limitBrowser If flags should only be removed from a specific browser
 * @returns {CompatStatement} The compatibility statement with all of the flags removed
 */
export const removeRedundantFlags = (
  key: string,
  value: CompatStatement,
  limitBrowser: BrowserName | null,
): CompatStatement => {
  if (key === '__compat') {
    for (const [browser, rawSupportData] of Object.entries(value.support)) {
      if (limitBrowser && browser != limitBrowser) {
        continue;
      }

      const supportData = Array.isArray(rawSupportData)
        ? rawSupportData
        : [rawSupportData];
      const result: SimpleSupportStatement[] = [];

      const simpleStatement = supportData.find((statement) => {
        const ignoreKeys = new Set([
          'version_removed',
          'notes',
          'partial_implementation',
        ]);
        const keys = Object.keys(statement).filter(
          (key) => !ignoreKeys.has(key),
        );
        return keys.length === 1;
      });

      for (let i = 0; i < supportData.length; i++) {
        let addData = true;

        if (supportData[i].flags) {
          const versionToCheck = getEarliestVersion(
            (supportData[i].version_removed as string) ||
              ((simpleStatement && simpleStatement.version_added) as string),
            (simpleStatement && simpleStatement.version_added) as string,
          );

          if (typeof versionToCheck === 'string') {
            const releaseDate =
              bcd.browsers[browser as BrowserName]?.releases[versionToCheck]
                ?.release_date;

            if (
              releaseDate &&
              (!(simpleStatement && simpleStatement.version_removed) ||
                compare(
                  (supportData[i].version_added as string).replace('≤', ''),
                  (simpleStatement.version_removed as string).replace('≤', ''),
                  '<',
                ))
            ) {
              addData = false;
            }
          }
        }

        if (addData) {
          result.push(supportData[i]);
        }
      }

      if (result.length == 1) {
        value.support[browser] = result[0];
      } else if (result.length == 0) {
        value.support[browser] = { version_added: false };
      } else {
        value.support[browser] = result;
      }
    }
  }
  return value;
};

/**
 * Removes redundant flags from the compatibility data of a specified file
 *
 * @param {string} filename The filename containing compatibility info
 * @param {BrowserName?} limitBrowser If flags should only be removed from a specific browser
 */
export const fixRedundantFlags = (
  filename: string,
  limitBrowser: BrowserName | null,
): void => {
  let actual = fs.readFileSync(filename, 'utf-8').trim();
  let expected = JSON.stringify(
    JSON.parse(actual, (k, v) => removeRedundantFlags(k, v, limitBrowser)),
    null,
    2,
  );

  if (IS_WINDOWS) {
    // prevent false positives from git.core.autocrlf on Windows
    actual = actual.replace(/\r/g, '');
    expected = expected.replace(/\r/g, '');
  }

  if (actual !== expected) {
    fs.writeFileSync(filename, expected + '\n', 'utf-8');
  }
};

/**
 * Removes redundant flags from the compatibility data of specified files/folders
 *
 * @param {string[]} files_or_folders The files and/or folders to run removal on
 * @param {BrowserName?} browser If flags should only be removed from a specific
 */
const main = (
  files_or_folders: string[],
  browser: BrowserName | null,
): void => {
  for (let file of files_or_folders) {
    if (file.indexOf(dirname) !== 0) {
      file = path.resolve(dirname, '..', file);
    }

    if (!fs.existsSync(file)) {
      continue; // Ignore non-existent files
    }

    if (fs.statSync(file).isFile()) {
      if (path.extname(file) === '.json') {
        fixRedundantFlags(file, browser);
      }

      continue;
    }

    const subFiles = fs
      .readdirSync(file)
      .map((subfile) => path.join(file, subfile));

    main(subFiles, browser);
  }
};

if (esMain(import.meta)) {
  const { argv } = yargs(hideBin(process.argv)).command(
    '$0 [file]',
    'Remove data for redundant flags',
    (yargs) => {
      yargs
        .positional('file', {
          describe: 'The file(s) and/or folder(s) to test',
          type: 'string',
          array: true,
          default: [
            'api',
            'css',
            'html',
            'http',
            'svg',
            'javascript',
            'mathml',
            'test',
            'webdriver',
            'webextensions',
          ],
        })
        .option('browser', {
          alias: 'b',
          describe: 'The browser to test for',
          type: 'string',
          default: null,
        });
    },
  );

  main((argv as any).file, (argv as any).browser);
}
