text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow strict-local
   * @format
   */
  
  // flowlint ambiguous-object-type:error
  
  'use strict';
  
  const RelayConcreteNode = require('../util/RelayConcreteNode');
  const RelayFeatureFlags = require('../util/RelayFeatureFlags');
  const RelayModernRecord = require('./RelayModernRecord');
  const RelayStoreUtils = require('./RelayStoreUtils');
  
  const cloneRelayHandleSourceField = require('./cloneRelayHandleSourceField');
  const getOperation = require('../util/getOperation');
  const invariant = require('invariant');
  
  const {REACT_FLIGHT_QUERIES_STORAGE_KEY} = require('./ReactFlight');
  const {generateTypeID} = require('./TypeID');
  
  import type {ReactFlightPayloadQuery} from '../network/RelayNetworkTypes';
  import type {
    NormalizationFlightField,
    NormalizationLinkedField,
    NormalizationModuleImport,
    NormalizationNode,
    NormalizationSelection,
  } from '../util/NormalizationNode';
  import type {DataID, Variables} from '../util/RelayRuntimeTypes';
  import type {
    NormalizationSelector,
    OperationLoader,
    Record,
    RecordSource,
  } from './RelayStoreTypes';
  
  const {
    CONDITION,
    CLIENT_EXTENSION,
    DEFER,
    FLIGHT_FIELD,
    FRAGMENT_SPREAD,
    INLINE_FRAGMENT,
    LINKED_FIELD,
    MODULE_IMPORT,
    LINKED_HANDLE,
    SCALAR_FIELD,
    SCALAR_HANDLE,
    STREAM,
    TYPE_DISCRIMINATOR,
  } = RelayConcreteNode;
  const {ROOT_ID, getStorageKey, getModuleOperationKey} = RelayStoreUtils;
  
  function mark(
    recordSource: RecordSource,
    selector: NormalizationSelector,
    references: Set<DataID>,
    operationLoader: ?OperationLoader,
  ): void {
    const {dataID, node, variables} = selector;
    const marker = new RelayReferenceMarker(
      recordSource,
      variables,
      references,
      operationLoader,
    );
    marker.mark(node, dataID);
  }
  
  /**
   * @private
   */
  class RelayReferenceMarker {
    _operationLoader: OperationLoader | null;
    _operationName: ?string;
    _recordSource: RecordSource;
    _references: Set<DataID>;
    _variables: Variables;
  
    constructor(
      recordSource: RecordSource,
      variables: Variables,
      references: Set<DataID>,
      operationLoader: ?OperationLoader,
    ) {
      this._operationLoader = operationLoader ?? null;
      this._operationName = null;
      this._recordSource = recordSource;
      this._references = references;
      this._variables = variables;
    }
  
    mark(node: NormalizationNode, dataID: DataID): void {
      if (node.kind === 'Operation' || node.kind === 'SplitOperation') {
        this._operationName = node.name;
      }
      this._traverse(node, dataID);
    }
  
    _traverse(node: NormalizationNode, dataID: DataID): void {
      this._references.add(dataID);
      const record = this._recordSource.get(dataID);
      if (record == null) {
        return;
      }
      this._traverseSelections(node.selections, record);
    }
  
    _getVariableValue(name: string): mixed {
      invariant(
        this._variables.hasOwnProperty(name),
        'RelayReferenceMarker(): Undefined variable `%s`.',
        name,
      );
      return this._variables[name];
    }
  
    _traverseSelections(
      selections: $ReadOnlyArray<NormalizationSelection>,
      record: Record,
    ): void {
      selections.forEach(selection => {
        /* eslint-disable no-fallthrough */
        switch (selection.kind) {
          case LINKED_FIELD:
            if (selection.plural) {
              this._traversePluralLink(selection, record);
            } else {
              this._traverseLink(selection, record);
            }
            break;
          case CONDITION:
            const conditionValue = this._getVariableValue(selection.condition);
            if (conditionValue === selection.passingValue) {
              this._traverseSelections(selection.selections, record);
            }
            break;
          case INLINE_FRAGMENT:
            if (selection.abstractKey == null) {
              const typeName = RelayModernRecord.getType(record);
              if (typeName != null && typeName === selection.type) {
                this._traverseSelections(selection.selections, record);
              }
            } else if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
              const typeName = RelayModernRecord.getType(record);
              const typeID = generateTypeID(typeName);
              this._references.add(typeID);
              this._traverseSelections(selection.selections, record);
            } else {
              this._traverseSelections(selection.selections, record);
            }
            break;
          // $FlowFixMe[incompatible-type]
          case FRAGMENT_SPREAD:
            invariant(
              false,
              'RelayReferenceMarker(): Unexpected fragment spread `...%s`, ' +
                'expected all fragments to be inlined.',
              selection.name,
            );
          case LINKED_HANDLE:
            // The selections for a "handle" field are the same as those of the
            // original linked field where the handle was applied. Reference marking
            // therefore requires traversing the original field selections against
            // the synthesized client field.
            //
            // TODO: Instead of finding the source field in `selections`, change
            // the concrete structure to allow shared subtrees, and have the linked
            // handle directly refer to the same selections as the LinkedField that
            // it was split from.
            const handleField = cloneRelayHandleSourceField(
              selection,
              selections,
              this._variables,
            );
            if (handleField.plural) {
              this._traversePluralLink(handleField, record);
            } else {
              this._traverseLink(handleField, record);
            }
            break;
          case DEFER:
          case STREAM:
            this._traverseSelections(selection.selections, record);
            break;
          case SCALAR_FIELD:
          case SCALAR_HANDLE:
            break;
          case TYPE_DISCRIMINATOR: {
            if (RelayFeatureFlags.ENABLE_PRECISE_TYPE_REFINEMENT) {
              const typeName = RelayModernRecord.getType(record);
              const typeID = generateTypeID(typeName);
              this._references.add(typeID);
            }
            break;
          }
          case MODULE_IMPORT:
            this._traverseModuleImport(selection, record);
            break;
          case CLIENT_EXTENSION:
            this._traverseSelections(selection.selections, record);
            break;
          case FLIGHT_FIELD:
            if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
              this._traverseFlightField(selection, record);
            } else {
              throw new Error('Flight fields are not yet supported.');
            }
            break;
          default:
            (selection: empty);
            invariant(
              false,
              'RelayReferenceMarker: Unknown AST node `%s`.',
              selection,
            );
        }
      });
    }
  
    _traverseModuleImport(
      moduleImport: NormalizationModuleImport,
      record: Record,
    ): void {
      const operationLoader = this._operationLoader;
      invariant(
        operationLoader !== null,
        'RelayReferenceMarker: Expected an operationLoader to be configured when using `@module`. ' +
          'Could not load fragment `%s` in operation `%s`.',
        moduleImport.fragmentName,
        this._operationName ?? '(unknown)',
      );
      const operationKey = getModuleOperationKey(moduleImport.documentName);
      const operationReference = RelayModernRecord.getValue(record, operationKey);
      if (operationReference == null) {
        return;
      }
      const normalizationRootNode = operationLoader.get(operationReference);
      if (normalizationRootNode != null) {
        const selections = getOperation(normalizationRootNode).selections;
        this._traverseSelections(selections, record);
      }
      // Otherwise, if the operation is not available, we assume that the data
      // cannot have been processed yet and therefore isn't in the store to
      // begin with.
    }
  
    _traverseLink(field: NormalizationLinkedField, record: Record): void {
      const storageKey = getStorageKey(field, this._variables);
      const linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
  
      if (linkedID == null) {
        return;
      }
      this._traverse(field, linkedID);
    }
  
    _traversePluralLink(field: NormalizationLinkedField, record: Record): void {
      const storageKey = getStorageKey(field, this._variables);
      const linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
  
      if (linkedIDs == null) {
        return;
      }
      linkedIDs.forEach(linkedID => {
        if (linkedID != null) {
          this._traverse(field, linkedID);
        }
      });
    }
  
    _traverseFlightField(field: NormalizationFlightField, record: Record): void {
      const storageKey = getStorageKey(field, this._variables);
      const linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
      if (linkedID == null) {
        return;
      }
      this._references.add(linkedID);
  
      const reactFlightClientResponseRecord = this._recordSource.get(linkedID);
  
      if (reactFlightClientResponseRecord == null) {
        return;
      }
  
      const reachableQueries = RelayModernRecord.getValue(
        reactFlightClientResponseRecord,
        REACT_FLIGHT_QUERIES_STORAGE_KEY,
      );
  
      if (!Array.isArray(reachableQueries)) {
        return;
      }
  
      const operationLoader = this._operationLoader;
      invariant(
        operationLoader !== null,
        'DataChecker: Expected an operationLoader to be configured when using ' +
          'React Flight',
      );
      // In Flight, the variables that are in scope for reachable queries aren't
      // the same as what's in scope for the outer query.
      const prevVariables = this._variables;
      // $FlowFixMe[incompatible-cast]
      for (const query of (reachableQueries: Array<ReactFlightPayloadQuery>)) {
        this._variables = query.variables;
        const operationReference = query.module;
        const normalizationRootNode = operationLoader.get(operationReference);
        if (normalizationRootNode != null) {
          const operation = getOperation(normalizationRootNode);
          this._traverse(operation, ROOT_ID);
        }
      }
      this._variables = prevVariables;
    }
  }
  
  module.exports = {mark};
  