text: // @flow
  
  import browserslist from "browserslist";
  import semver from "semver";
  import { semverify, isUnreleasedVersion, getLowestUnreleased } from "./utils";
  import { objectToBrowserslist } from "./normalize-options";
  import browserModulesData from "../data/built-in-modules.json";
  import type { Targets } from "./types";
  
  const browserNameMap = {
    android: "android",
    chrome: "chrome",
    and_chr: "chrome",
    edge: "edge",
    firefox: "firefox",
    ie: "ie",
    ios_saf: "ios",
    safari: "safari",
  };
  
  const isBrowsersQueryValid = (browsers: string | Array<string>): boolean =>
    typeof browsers === "string" || Array.isArray(browsers);
  
  export const semverMin = (first: ?string, second: string): string => {
    return first && semver.lt(first, second) ? first : second;
  };
  
  const mergeBrowsers = (fromQuery: Targets, fromTarget: Targets) => {
    return Object.keys(fromTarget).reduce((queryObj, targKey) => {
      if (targKey !== "browsers") {
        queryObj[targKey] = fromTarget[targKey];
      }
      return queryObj;
    }, fromQuery);
  };
  
  const getLowestVersions = (browsers: Array<string>): Targets => {
    return browsers.reduce((all: Object, browser: string): Object => {
      const [browserName, browserVersion] = browser.split(" ");
      const normalizedBrowserName = browserNameMap[browserName];
  
      if (!normalizedBrowserName) {
        return all;
      }
  
      try {
        // Browser version can return as "10.0-10.2"
        const splitVersion = browserVersion.split("-")[0].toLowerCase();
  
        if (isUnreleasedVersion(splitVersion, browserName)) {
          all[normalizedBrowserName] = getLowestUnreleased(
            all[normalizedBrowserName],
            splitVersion,
            browserName,
          );
        }
  
        const parsedBrowserVersion = semverify(splitVersion);
  
        all[normalizedBrowserName] = semverMin(
          all[normalizedBrowserName],
          parsedBrowserVersion,
        );
      } catch (e) {}
  
      return all;
    }, {});
  };
  
  const outputDecimalWarning = (decimalTargets: Array<Object>): void => {
    if (!decimalTargets || !decimalTargets.length) {
      return;
    }
  
    console.log("Warning, the following targets are using a decimal version:");
    console.log("");
    decimalTargets.forEach(({ target, value }) =>
      console.log(`  ${target}: ${value}`),
    );
    console.log("");
    console.log(
      "We recommend using a string for minor/patch versions to avoid numbers like 6.10",
    );
    console.log("getting parsed as 6.1, which can lead to unexpected behavior.");
    console.log("");
  };
  
  const targetParserMap = {
    __default: (target, value) => {
      const version = isUnreleasedVersion(value, target)
        ? value.toLowerCase()
        : semverify(value);
      return [target, version];
    },
  
    // Parse `node: true` and `node: "current"` to version
    node: (target, value) => {
      const parsed =
        value === true || value === "current"
          ? process.versions.node
          : semverify(value);
  
      return [target, parsed];
    },
  };
  
  type ParsedResult = {
    targets: Targets,
    decimalWarnings: Array<Object>,
  };
  const getTargets = (targets: Object = {}, options: Object = {}): Targets => {
    const targetOpts: Targets = {};
  
    // `esmodules` as a target indicates the specific set of browsers supporting ES Modules.
    // These values OVERRIDE the `browsers` field.
    if (targets.esmodules) {
      const supportsESModules = browserModulesData["es6.module"];
      targets.browsers = Object.keys(supportsESModules)
        .map(browser => `${browser} ${supportsESModules[browser]}`)
        .join(", ");
    }
    // Parse browsers target via browserslist;
    const queryIsValid = isBrowsersQueryValid(targets.browsers);
    const browsersquery = queryIsValid ? targets.browsers : null;
    if (queryIsValid || !options.ignoreBrowserslistConfig) {
      browserslist.defaults = objectToBrowserslist(targets);
  
      const browsers = browserslist(browsersquery, { path: options.configPath });
      const queryBrowsers = getLowestVersions(browsers);
      targets = mergeBrowsers(queryBrowsers, targets);
    }
    // Parse remaining targets
    const parsed = Object.keys(targets)
      .filter(value => value !== "esmodules")
      .sort()
      .reduce(
        (results: ParsedResult, target: string): ParsedResult => {
          if (target !== "browsers") {
            const value = targets[target];
  
            // Warn when specifying minor/patch as a decimal
            if (typeof value === "number" && value % 1 !== 0) {
              results.decimalWarnings.push({ target, value });
            }
  
            // Check if we have a target parser?
            const parser = targetParserMap[target] || targetParserMap.__default;
            const [parsedTarget, parsedValue] = parser(target, value);
  
            if (parsedValue) {
              // Merge (lowest wins)
              results.targets[parsedTarget] = parsedValue;
            }
          }
  
          return results;
        },
        {
          targets: targetOpts,
          decimalWarnings: [],
        },
      );
  
    outputDecimalWarning(parsed.decimalWarnings);
  
    return parsed.targets;
  };
  
  export default getTargets;
  