text: // @flow strict-local
  import type {
    Environment as IEnvironment,
    EnvironmentContext,
    EnvironmentFeature,
    Engines,
    OutputFormat,
    PackageName,
    VersionMap,
    SourceLocation,
    SourceType,
    TargetSourceMapOptions,
  } from '@parcel/types';
  import type {Environment as InternalEnvironment} from '../types';
  import nullthrows from 'nullthrows';
  import browserslist from 'browserslist';
  import semver from 'semver';
  
  export const BROWSER_ENVS: Set<string> = new Set<string>([
    'browser',
    'web-worker',
    'service-worker',
    'electron-renderer',
  ]);
  const ELECTRON_ENVS = new Set(['electron-main', 'electron-renderer']);
  const NODE_ENVS = new Set(['node', ...ELECTRON_ENVS]);
  const WORKER_ENVS = new Set(['web-worker', 'service-worker']);
  const ISOLATED_ENVS = WORKER_ENVS;
  
  const ALL_BROWSERS = [
    'chrome',
    'and_chr',
    'edge',
    'firefox',
    'and_ff',
    'safari',
    'ios',
    'samsung',
    'opera',
    'ie',
    'op_mini',
    'blackberry',
    'op_mob',
    'ie_mob',
    'and_uc',
    'and_qq',
    'baidu',
    'kaios',
  ];
  
  const supportData = {
    esmodules: {
      edge: '16',
      firefox: '60',
      chrome: '61',
      safari: '11',
      opera: '48',
      ios: '11',
      android: '76',
      and_chr: '76',
      and_ff: '68',
      samsung: '8.2',
    },
    'dynamic-import': {
      edge: '76',
      firefox: '67',
      chrome: '63',
      safari: '11.1',
      opera: '50',
      ios: '11.3',
      android: '63',
      and_chr: '63',
      and_ff: '67',
      samsung: '8',
    },
    'worker-module': {
      edge: '80',
      chrome: '80',
      opera: '67',
      android: '81',
      and_chr: '86',
    },
    'service-worker-module': {
      // TODO: Safari 14.1??
    },
  };
  
  const internalEnvironmentToEnvironment: WeakMap<
    InternalEnvironment,
    Environment,
  > = new WeakMap();
  const _environmentToInternalEnvironment: WeakMap<
    IEnvironment,
    InternalEnvironment,
  > = new WeakMap();
  export function environmentToInternalEnvironment(
    environment: IEnvironment,
  ): InternalEnvironment {
    return nullthrows(_environmentToInternalEnvironment.get(environment));
  }
  
  export default class Environment implements IEnvironment {
    #environment /*: InternalEnvironment */;
  
    constructor(env: InternalEnvironment): Environment {
      let existing = internalEnvironmentToEnvironment.get(env);
      if (existing != null) {
        return existing;
      }
  
      this.#environment = env;
      _environmentToInternalEnvironment.set(this, env);
      internalEnvironmentToEnvironment.set(env, this);
      return this;
    }
  
    get id(): string {
      return this.#environment.id;
    }
  
    get context(): EnvironmentContext {
      return this.#environment.context;
    }
  
    get engines(): Engines {
      return this.#environment.engines;
    }
  
    get includeNodeModules():
      | boolean
      | Array<PackageName>
      | {[PackageName]: boolean, ...} {
      return this.#environment.includeNodeModules;
    }
  
    get outputFormat(): OutputFormat {
      return this.#environment.outputFormat;
    }
  
    get sourceType(): SourceType {
      return this.#environment.sourceType;
    }
  
    get isLibrary(): boolean {
      return this.#environment.isLibrary;
    }
  
    get shouldOptimize(): boolean {
      return this.#environment.shouldOptimize;
    }
  
    get shouldScopeHoist(): boolean {
      return this.#environment.shouldScopeHoist;
    }
  
    get sourceMap(): ?TargetSourceMapOptions {
      return this.#environment.sourceMap;
    }
  
    get loc(): ?SourceLocation {
      return this.#environment.loc;
    }
  
    isBrowser(): boolean {
      return BROWSER_ENVS.has(this.#environment.context);
    }
  
    isNode(): boolean {
      return NODE_ENVS.has(this.#environment.context);
    }
  
    isElectron(): boolean {
      return ELECTRON_ENVS.has(this.#environment.context);
    }
  
    isIsolated(): boolean {
      return ISOLATED_ENVS.has(this.#environment.context);
    }
  
    isWorker(): boolean {
      return WORKER_ENVS.has(this.#environment.context);
    }
  
    matchesEngines(
      minVersions: VersionMap,
      defaultValue?: boolean = false,
    ): boolean {
      // Determine if the environment matches some minimum version requirements.
      // For browsers, we run a browserslist query with and without the minimum
      // required browsers and compare the lists. For node, we just check semver.
      if (this.isBrowser() && this.engines.browsers != null) {
        let targetBrowsers = this.engines.browsers;
        let browsers =
          targetBrowsers != null && !Array.isArray(targetBrowsers)
            ? [targetBrowsers]
            : targetBrowsers;
  
        // If outputting esmodules, exclude browsers without support.
        if (this.outputFormat === 'esmodule') {
          browsers = [...browsers, ...getExcludedBrowsers(supportData.esmodules)];
        }
  
        let matchedBrowsers = browserslist(browsers);
        let minBrowsers = getExcludedBrowsers(minVersions);
        let withoutMinBrowsers = browserslist([...browsers, ...minBrowsers]);
        return matchedBrowsers.length === withoutMinBrowsers.length;
      } else if (this.isNode() && this.engines.node != null) {
        return (
          minVersions.node != null &&
          !semver.intersects(`< ${minVersions.node}`, this.engines.node)
        );
      }
  
      return defaultValue;
    }
  
    supports(feature: EnvironmentFeature, defaultValue?: boolean): boolean {
      let engines = supportData[feature];
      if (!engines) {
        throw new Error('Unknown environment feature: ' + feature);
      }
  
      return this.matchesEngines(engines, defaultValue);
    }
  }
  
  function getExcludedBrowsers(minVersions: VersionMap) {
    let browsers = [];
    for (let browser of ALL_BROWSERS) {
      let version = minVersions[browser];
      if (version) {
        browsers.push(`not ${browser} < ${version}`);
      } else {
        browsers.push(`not ${browser} > 0`);
      }
    }
  
    return browsers;
  }
  