function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/

/* global window */
import * as React from 'react';
import { useUID } from 'react-uid';
import { useStyletron } from '../styles';
import { getOverrides } from '../helpers/overrides';
import { isFocusVisible, forkFocus, forkBlur } from '../utils/focusVisible';
import { FILL } from './constants';
import { StyledRoot, StyledSegment, StyledArtworkContainer, StyledActive, StyledSegmentList, StyledLabel, StyledDescription } from './styled-components';
import { getSegmentId, isRTL } from './utils';
const KEYBOARD_ACTION = {
  next: 'next',
  previous: 'previous'
};

const getLayoutParams = el => {
  if (!el) {
    return {
      length: 0,
      distance: 0
    };
  }

  let {
    width
  } = el.getBoundingClientRect();
  width = Math.floor(width); // Note we're using getBoundingClientRect to take into account
  // the borders.

  return {
    length: width,
    distance: el.offsetLeft
  };
};

const scrollParentToCentreTarget = targetNode => {
  const {
    x: parentX,
    y: parentY,
    width: parentWidth,
    height: parentHeight
  } = targetNode.parentNode.getBoundingClientRect();
  const {
    x: childX,
    y: childY,
    width: childWidth,
    height: childHeight
  } = targetNode.getBoundingClientRect(); // get the position of the child centre, relative to parent

  const childCentre = {
    x: childX - parentX + childWidth / 2,
    y: childY - parentY + childHeight / 2
  }; // aim for the centre of the child to be the centre of the parent

  const {
    scrollLeft,
    scrollTop
  } = targetNode.parentNode;
  const target = {
    x: scrollLeft + childCentre.x - parentWidth / 2,
    y: scrollTop + childCentre.y - parentHeight / 2
  }; // ignore out of bounds, the browser will manage this for us

  targetNode.parentNode.scroll(target.x, target.y);
};

export function SegmentedControl({
  activeKey = '0',
  disabled = false,
  children,
  fill = FILL.intrinsic,
  activateOnFocus = true,
  onChange,
  overrides = {},
  uid: customUid = null,
  width,
  height
}) {
  // Create unique id prefix for this segments component
  const generatedUid = useUID();
  const uid = customUid || generatedUid; // Unpack overrides

  const {
    Root: RootOverrides,
    Active: ActiveOverrides
  } = overrides;
  const [Root, RootProps] = getOverrides(RootOverrides, StyledRoot);
  const [Active, ActiveProps] = getOverrides(ActiveOverrides, StyledActive);
  const [SegmentList, SegmentListProps] = getOverrides(overrides.SegmentList, StyledSegmentList); // Count key updates
  // We disable a few things until after first mount:
  // - the highlight animation, avoiding an initial slide-in
  // - smooth scrolling active segment into view

  const [keyUpdated, setKeyUpdated] = React.useState(0);
  React.useEffect(() => {
    setKeyUpdated(keyUpdated + 1);
  }, [activeKey]); // Positioning the highlight.

  const activeSegmentRef = React.useRef();
  const [highlightLayout, setHighlightLayout] = React.useState({
    length: 0,
    distance: 0
  }); // Create a shared, memoized callback for segments to call on resize.

  const updateHighlight = React.useCallback(() => {
    if (activeSegmentRef.current) {
      setHighlightLayout(getLayoutParams(activeSegmentRef.current));
    }
  }, [activeSegmentRef.current]); // Update highlight on key

  React.useEffect(updateHighlight, [activeSegmentRef.current]); // Scroll active segment into view when the parent has scrollbar on mount and
  // on key change (smooth scroll). Note, if the active key changes while
  // the segment is not in view, the page will scroll it into view.
  // TODO: replace with custom scrolling logic.

  React.useEffect(() => {
    // Flow needs this condition pulled out.
    if (activeSegmentRef.current) {
      if ( // @ts-expect-error todo(flow->ts) maybe parentElement?
      activeSegmentRef.current.parentNode.scrollWidth > // @ts-expect-error todo(flow->ts) maybe parentElement?
      activeSegmentRef.current.parentNode.clientWidth) {
        if (keyUpdated > 1) {
          activeSegmentRef.current.scrollIntoView({
            behavior: 'smooth',
            block: 'nearest',
            inline: 'nearest'
          });
        } else {
          scrollParentToCentreTarget(activeSegmentRef.current);
        }
      }
    }
  }, [activeSegmentRef.current]); // Collect shared styling props

  const sharedStylingProps = {
    $fill: fill
  }; // Helper for parsing directional keys
  // TODO(WPT-6473): move to universal keycode aliases

  const [, theme] = useStyletron();
  const parseKeyDown = React.useCallback(event => {
    if (isRTL(theme.direction)) {
      switch (event.keyCode) {
        case 39:
          return KEYBOARD_ACTION.previous;

        case 37:
          return KEYBOARD_ACTION.next;

        default:
          return null;
      }
    } else {
      switch (event.keyCode) {
        case 37:
          return KEYBOARD_ACTION.previous;

        case 39:
          return KEYBOARD_ACTION.next;

        default:
          return null;
      }
    }
  }, [theme.direction]);
  return /*#__PURE__*/React.createElement(Root, _extends({}, sharedStylingProps, RootProps, {
    $width: width,
    $height: height
  }), /*#__PURE__*/React.createElement(SegmentList, _extends({
    "data-baseweb": "segmented-list",
    role: "listbox",
    "aria-label": "segmented control"
  }, SegmentListProps), React.Children.map(children, (child, index) => {
    if (!child) return;
    return /*#__PURE__*/React.createElement(InternalSegment, _extends({
      childKey: child.key,
      childIndex: index,
      activeKey: activeKey,
      activeSegmentRef: activeSegmentRef,
      updateHighlight: updateHighlight,
      parseKeyDown: parseKeyDown,
      activateOnFocus: activateOnFocus,
      uid: uid,
      disabled: disabled,
      sharedStylingProps: sharedStylingProps,
      onChange: onChange
    }, child.props));
  }), /*#__PURE__*/React.createElement(Active, _extends({
    "data-baseweb": "segment-highlight",
    $length: highlightLayout.length,
    $distance: highlightLayout.distance // This avoids the segment sliding in from the side on mount
    ,
    $animate: keyUpdated > 1,
    "aria-hidden": "true",
    role: "presentation"
  }, sharedStylingProps, ActiveProps))));
}

function InternalSegment({
  childKey,
  childIndex,
  activeKey,
  activeSegmentRef,
  updateHighlight,
  parseKeyDown,
  activateOnFocus,
  uid,
  disabled,
  sharedStylingProps,
  onChange,
  ...props
}) {
  const key = childKey || String(childIndex);
  const isActive = key == activeKey;
  const {
    artwork: Artwork,
    overrides = {},
    segmentRef,
    onClick,
    label,
    description,
    ...restProps
  } = props; // A way to share our internal activeSegmentRef via the "segmentRef" prop.

  const ref = React.useRef();
  React.useImperativeHandle(segmentRef, () => {
    return isActive ? activeSegmentRef.current : ref.current;
  }); // Track segment dimensions in a ref after each render
  // This is used to compare params when the resize observer fires

  const segmentLayoutParams = React.useRef({
    length: 0,
    distance: 0
  });
  React.useEffect(() => {
    segmentLayoutParams.current = getLayoutParams(isActive ? activeSegmentRef.current : ref.current);
  }); // We need to potentially update the active segment highlight when the width or
  // placement changes for a segment so we listen for resize updates in each segment.

  React.useEffect(() => {
    if (window.ResizeObserver) {
      const observer = new window.ResizeObserver(entries => {
        if (entries[0] && entries[0].target) {
          const segmentLayoutParamsAfterResize = getLayoutParams(entries[0].target);

          if (segmentLayoutParamsAfterResize.length !== segmentLayoutParams.current.length || segmentLayoutParamsAfterResize.distance !== segmentLayoutParams.current.distance) {
            updateHighlight();
          }
        }
      });
      observer.observe(isActive ? activeSegmentRef.current : ref.current);
      return () => {
        observer.disconnect();
      };
    }
  }, [activeKey]);
  React.useEffect(updateHighlight, [label]); // Collect overrides

  const {
    Segment: SegmentOverrides,
    ArtworkContainer: ArtworkContainerOverrides,
    Label: LabelOverrides,
    Description: DescriptionOverrides
  } = overrides;
  const [Segment, SegmentProps] = getOverrides(SegmentOverrides, StyledSegment);
  const [ArtworkContainer, ArtworkContainerProps] = getOverrides(ArtworkContainerOverrides, StyledArtworkContainer);
  const [LabelContainer, LabelContainerProps] = getOverrides(LabelOverrides, StyledLabel);
  const [DescriptionContainer, DescriptionContainerProps] = getOverrides(DescriptionOverrides, StyledDescription); // Keyboard focus styling

  const [focusVisible, setFocusVisible] = React.useState(false);
  const handleFocus = React.useCallback(event => {
    if (isFocusVisible(event)) {
      setFocusVisible(true);
    }
  }, []);
  const handleBlur = React.useCallback( // eslint-disable-next-line @typescript-eslint/no-unused-vars
  event => {
    if (focusVisible !== false) {
      setFocusVisible(false);
    }
  }, [focusVisible]); // Keyboard focus management
  // @ts-expect-error todo(flow->ts): deps are required

  const handleKeyDown = React.useCallback(event => {
    // WAI-ARIA 1.1
    // https://www.w3.org/TR/wai-aria-practices-1.1/#segmentpanel
    // We use directional keys to iterate focus through SegmentedControl.
    // Find all segments eligible for focus
    const availableSegmentedControl = [...event.target.parentNode.childNodes].filter(node => !node.disabled && node.getAttribute('role') === 'option'); // Exit early if there are no other segments available

    if (availableSegmentedControl.length === 1) return; // Find segment to focus, looping to start/end of list if necessary

    const currentSegmentIndex = availableSegmentedControl.indexOf(event.target);
    const action = parseKeyDown(event);

    if (action) {
      let nextSegment;

      if (action === KEYBOARD_ACTION.previous) {
        if (availableSegmentedControl[currentSegmentIndex - 1]) {
          nextSegment = availableSegmentedControl[currentSegmentIndex - 1];
        } else {
          nextSegment = availableSegmentedControl[availableSegmentedControl.length - 1];
        }
      } else if (action === KEYBOARD_ACTION.next) {
        if (availableSegmentedControl[currentSegmentIndex + 1]) {
          nextSegment = availableSegmentedControl[currentSegmentIndex + 1];
        } else {
          nextSegment = availableSegmentedControl[0];
        }
      }

      if (nextSegment) {
        // Focus the segment
        nextSegment.focus(); // Optionally activate the segment

        if (activateOnFocus) {
          nextSegment.click();
        }
      }
    }
  });
  return /*#__PURE__*/React.createElement(Segment, _extends({
    "data-baseweb": "segment",
    key: key,
    id: getSegmentId(uid, key),
    role: "option",
    onKeyDown: handleKeyDown,
    "aria-selected": isActive,
    tabIndex: isActive ? '0' : '-1',
    ref: isActive ? activeSegmentRef : ref,
    disabled: !isActive && disabled,
    type: "button" // so it doesn't trigger a submit when used inside forms
    ,
    $focusVisible: focusVisible,
    $isActive: isActive,
    $hasArtwork: !!Artwork,
    $hasLabel: !!label
  }, sharedStylingProps, restProps, SegmentProps, {
    onClick: event => {
      if (typeof onChange === 'function') onChange({
        activeKey: key
      });
      if (typeof onClick === 'function') onClick(event);
    },
    onFocus: forkFocus({ ...restProps,
      ...SegmentProps
    }, handleFocus),
    onBlur: forkBlur({ ...restProps,
      ...SegmentProps
    }, handleBlur)
  }), Artwork ? /*#__PURE__*/React.createElement(ArtworkContainer, _extends({
    "data-baseweb": "artwork-container"
  }, sharedStylingProps, ArtworkContainerProps), /*#__PURE__*/React.createElement(Artwork, {
    size: 20,
    color: "contentPrimary"
  })) : null, label ? /*#__PURE__*/React.createElement(LabelContainer, _extends({
    $hasArtwork: !!Artwork
  }, LabelContainerProps), label ? label : key) : null, description ? /*#__PURE__*/React.createElement(DescriptionContainer, _extends({}, DescriptionOverrides, DescriptionContainerProps), description) : null);
}