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
   */
  
  import {copy} from 'clipboard-js';
  import * as React from 'react';
  import {useCallback, useContext, useRef, useState} from 'react';
  import {BridgeContext, StoreContext} from '../context';
  import Button from '../Button';
  import ButtonIcon from '../ButtonIcon';
  import EditableValue from './EditableValue';
  import ExpandCollapseToggle from './ExpandCollapseToggle';
  import {InspectedElementContext} from './InspectedElementContext';
  import KeyValue from './KeyValue';
  import {getMetaValueLabel, serializeHooksForCopy} from '../utils';
  import styles from './HooksTree.css';
  import useContextMenu from '../../ContextMenu/useContextMenu';
  import {meta} from '../../../hydration';
  
  import type {InspectPath} from './SelectedElement';
  import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
  
  type HooksTreeViewProps = {|
    canEditHooks: boolean,
    hooks: HooksTree | null,
    id: number,
  |};
  
  export function HooksTreeView({canEditHooks, hooks, id}: HooksTreeViewProps) {
    const {getInspectedElementPath} = useContext(InspectedElementContext);
    const inspectPath = useCallback(
      (path: Array<string | number>) => {
        getInspectedElementPath(id, ['hooks', ...path]);
      },
      [getInspectedElementPath, id],
    );
    const handleCopy = useCallback(() => copy(serializeHooksForCopy(hooks)), [
      hooks,
    ]);
  
    if (hooks === null) {
      return null;
    } else {
      return (
        <div className={styles.HooksTreeView}>
          <div className={styles.HeaderRow}>
            <div className={styles.Header}>hooks</div>
            <Button onClick={handleCopy} title="Copy to clipboard">
              <ButtonIcon type="copy" />
            </Button>
          </div>
          <InnerHooksTreeView
            canEditHooks={canEditHooks}
            hooks={hooks}
            id={id}
            inspectPath={inspectPath}
            path={[]}
          />
        </div>
      );
    }
  }
  
  type InnerHooksTreeViewProps = {|
    canEditHooks: boolean,
    hooks: HooksTree,
    id: number,
    inspectPath: InspectPath,
    path: Array<string | number>,
  |};
  
  export function InnerHooksTreeView({
    canEditHooks,
    hooks,
    id,
    inspectPath,
    path,
  }: InnerHooksTreeViewProps) {
    // $FlowFixMe "Missing type annotation for U" whatever that means
    return hooks.map((hook, index) => (
      <HookView
        key={index}
        canEditHooks={canEditHooks}
        hook={hooks[index]}
        id={id}
        inspectPath={inspectPath}
        path={path.concat([index])}
      />
    ));
  }
  
  type HookViewProps = {|
    canEditHooks: boolean,
    hook: HooksNode,
    id: number,
    inspectPath: InspectPath,
    path: Array<string | number>,
  |};
  
  function HookView({canEditHooks, hook, id, inspectPath, path}: HookViewProps) {
    const {name, id: hookID, isStateEditable, subHooks, value} = hook;
  
    const bridge = useContext(BridgeContext);
    const store = useContext(StoreContext);
  
    const [isOpen, setIsOpen] = useState<boolean>(false);
  
    const toggleIsOpen = useCallback(
      () => setIsOpen(prevIsOpen => !prevIsOpen),
      [],
    );
  
    const contextMenuTriggerRef = useRef(null);
  
    useContextMenu({
      data: {
        path: ['hooks', ...path],
        type:
          hook !== null &&
          typeof hook === 'object' &&
          hook.hasOwnProperty(meta.type)
            ? hook[(meta.type: any)]
            : typeof value,
      },
      id: 'SelectedElement',
      ref: contextMenuTriggerRef,
    });
  
    if (hook.hasOwnProperty(meta.inspected)) {
      // This Hook is too deep and hasn't been hydrated.
      if (__DEV__) {
        console.warn('Unexpected dehydrated hook; this is a DevTools error.');
      }
      return (
        <div className={styles.Hook}>
          <div className={styles.NameValueRow}>
            <span className={styles.TruncationIndicator}>...</span>
          </div>
        </div>
      );
    }
  
    const isCustomHook = subHooks.length > 0;
  
    const type = typeof value;
  
    let displayValue;
    let isComplexDisplayValue = false;
  
    // Format data for display to mimic the props/state/context for now.
    if (type === 'string') {
      displayValue = `"${((value: any): string)}"`;
    } else if (type === 'boolean') {
      displayValue = value ? 'true' : 'false';
    } else if (type === 'number') {
      displayValue = value;
    } else if (value === null) {
      displayValue = 'null';
    } else if (value === undefined) {
      displayValue = null;
    } else if (Array.isArray(value)) {
      isComplexDisplayValue = true;
      displayValue = 'Array';
    } else if (type === 'object') {
      isComplexDisplayValue = true;
      displayValue = 'Object';
    }
  
    if (isCustomHook) {
      const subHooksView = Array.isArray(subHooks) ? (
        <InnerHooksTreeView
          canEditHooks={canEditHooks}
          hooks={subHooks}
          id={id}
          inspectPath={inspectPath}
          path={path.concat(['subHooks'])}
        />
      ) : (
        <KeyValue
          depth={1}
          alphaSort={false}
          inspectPath={inspectPath}
          name="subHooks"
          path={path.concat(['subHooks'])}
          pathRoot="hooks"
          value={subHooks}
        />
      );
  
      if (isComplexDisplayValue) {
        return (
          <div className={styles.Hook}>
            <div ref={contextMenuTriggerRef} className={styles.NameValueRow}>
              <ExpandCollapseToggle isOpen={isOpen} setIsOpen={setIsOpen} />
              <span
                onClick={toggleIsOpen}
                className={name !== '' ? styles.Name : styles.NameAnonymous}>
                {name || 'Anonymous'}
              </span>
              <span className={styles.Value} onClick={toggleIsOpen}>
                {isOpen || getMetaValueLabel(value)}
              </span>
            </div>
            <div className={styles.Children} hidden={!isOpen}>
              <KeyValue
                depth={1}
                alphaSort={false}
                inspectPath={inspectPath}
                name="DebugValue"
                path={path.concat(['value'])}
                pathRoot="hooks"
                value={value}
              />
              {subHooksView}
            </div>
          </div>
        );
      } else {
        return (
          <div className={styles.Hook}>
            <div ref={contextMenuTriggerRef} className={styles.NameValueRow}>
              <ExpandCollapseToggle isOpen={isOpen} setIsOpen={setIsOpen} />
              <span
                onClick={toggleIsOpen}
                className={name !== '' ? styles.Name : styles.NameAnonymous}>
                {name || 'Anonymous'}
              </span>{' '}
              {/* $FlowFixMe */}
              <span className={styles.Value} onClick={toggleIsOpen}>
                {displayValue}
              </span>
            </div>
            <div className={styles.Children} hidden={!isOpen}>
              {subHooksView}
            </div>
          </div>
        );
      }
    } else {
      let overrideValueFn = null;
      // TODO Maybe read editable value from debug hook?
      if (canEditHooks && isStateEditable && hookID !== null) {
        overrideValueFn = (
          absolutePath: Array<string | number>,
          newValue: any,
        ) => {
          const rendererID = store.getRendererIDForElement(id);
          if (rendererID !== null) {
            bridge.send('overrideHookState', {
              id,
              hookID,
              // Hooks override function expects a relative path for the specified hook (id),
              // starting with its id within the (flat) hooks list structure.
              // This relative path does not include the fake tree structure DevTools uses for display,
              // so it's important that we remove that part of the path before sending the update.
              path: absolutePath.slice(path.length + 1),
              rendererID,
              value: newValue,
            });
          }
        };
      }
  
      if (isComplexDisplayValue) {
        return (
          <div className={styles.Hook}>
            <KeyValue
              depth={1}
              alphaSort={false}
              inspectPath={inspectPath}
              name={name}
              overrideValueFn={overrideValueFn}
              path={path.concat(['value'])}
              pathRoot="hooks"
              value={value}
            />
          </div>
        );
      } else {
        return (
          <div className={styles.Hook}>
            <div ref={contextMenuTriggerRef} className={styles.NameValueRow}>
              <span className={styles.ExpandCollapseToggleSpacer} />
              <span
                className={
                  typeof overrideValueFn === 'function'
                    ? styles.EditableName
                    : styles.Name
                }>
                {name}
              </span>
              {typeof overrideValueFn === 'function' ? (
                <EditableValue
                  overrideValueFn={overrideValueFn}
                  path={[]}
                  value={value}
                />
              ) : (
                // $FlowFixMe Cannot create span element because in property children
                <span className={styles.Value}>{displayValue}</span>
              )}
            </div>
          </div>
        );
      }
    }
  }
  
  // $FlowFixMe
  export default React.memo(HooksTreeView);
  