import { createElement, Fragment } from "react";
/**
 * External dependencies
 */
import classnames from 'classnames';
import { paramCase as kebabCase } from 'change-case';

/**
 * WordPress dependencies
 */
import { useState, useRef, useEffect, useCallback, useMemo } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { lineSolid, moreVertical, plus } from '@wordpress/icons';
import { __experimentalUseFocusOutside as useFocusOutside, useDebounce } from '@wordpress/compose';

/**
 * Internal dependencies
 */
import Button from '../button';
import { ColorPicker } from '../color-picker';
import { FlexItem } from '../flex';
import { HStack } from '../h-stack';
import { ItemGroup } from '../item-group';
import { VStack } from '../v-stack';
import GradientPicker from '../gradient-picker';
import ColorPalette from '../color-palette';
import DropdownMenu from '../dropdown-menu';
import Popover from '../popover';
import { PaletteActionsContainer, PaletteEditStyles, PaletteHeading, PaletteHStackHeader, IndicatorStyled, PaletteItem, NameContainer, NameInputControl, DoneButton, RemoveButton } from './styles';
import { NavigableMenu } from '../navigable-container';
import { DEFAULT_GRADIENT } from '../custom-gradient-picker/constants';
import CustomGradientPicker from '../custom-gradient-picker';
export const DEFAULT_COLOR = '#000';
function NameInput({
  value,
  onChange,
  label
}) {
  return createElement(NameInputControl, {
    label: label,
    hideLabelFromVision: true,
    value: value,
    onChange: onChange
  });
}

/**
 * Returns a temporary name for a palette item in the format "Color + id".
 * To ensure there are no duplicate ids, this function checks all slugs for temporary names.
 * It expects slugs to be in the format: slugPrefix + color- + number.
 * It then sets the id component of the new name based on the incremented id of the highest existing slug id.
 *
 * @param elements   An array of color palette items.
 * @param slugPrefix The slug prefix used to match the element slug.
 *
 * @return A unique name for a palette item.
 */
export function getNameForPosition(elements, slugPrefix) {
  const temporaryNameRegex = new RegExp(`^${slugPrefix}color-([\\d]+)$`);
  const position = elements.reduce((previousValue, currentValue) => {
    if (typeof currentValue?.slug === 'string') {
      const matches = currentValue?.slug.match(temporaryNameRegex);
      if (matches) {
        const id = parseInt(matches[1], 10);
        if (id >= previousValue) {
          return id + 1;
        }
      }
    }
    return previousValue;
  }, 1);
  return sprintf( /* translators: %s: is a temporary id for a custom color */
  __('Color %s'), position);
}
function ColorPickerPopover({
  isGradient,
  element,
  onChange,
  popoverProps: receivedPopoverProps,
  onClose = () => {}
}) {
  const popoverProps = useMemo(() => ({
    shift: true,
    offset: 20,
    // Disabling resize as it would otherwise cause the popover to show
    // scrollbars while dragging the color picker's handle close to the
    // popover edge.
    resize: false,
    placement: 'left-start',
    ...receivedPopoverProps,
    className: classnames('components-palette-edit__popover', receivedPopoverProps?.className)
  }), [receivedPopoverProps]);
  return createElement(Popover, {
    ...popoverProps,
    onClose: onClose
  }, !isGradient && createElement(ColorPicker, {
    color: element.color,
    enableAlpha: true,
    onChange: newColor => {
      onChange({
        ...element,
        color: newColor
      });
    }
  }), isGradient && createElement("div", {
    className: "components-palette-edit__popover-gradient-picker"
  }, createElement(CustomGradientPicker, {
    __nextHasNoMargin: true,
    __experimentalIsRenderedInSidebar: true,
    value: element.gradient,
    onChange: newGradient => {
      onChange({
        ...element,
        gradient: newGradient
      });
    }
  })));
}
function Option({
  canOnlyChangeValues,
  element,
  onChange,
  isEditing,
  onStartEditing,
  onRemove,
  onStopEditing,
  popoverProps: receivedPopoverProps,
  slugPrefix,
  isGradient
}) {
  const focusOutsideProps = useFocusOutside(onStopEditing);
  const value = isGradient ? element.gradient : element.color;

  // Use internal state instead of a ref to make sure that the component
  // re-renders when the popover's anchor updates.
  const [popoverAnchor, setPopoverAnchor] = useState(null);
  const popoverProps = useMemo(() => ({
    ...receivedPopoverProps,
    // Use the custom palette color item as the popover anchor.
    anchor: popoverAnchor
  }), [popoverAnchor, receivedPopoverProps]);
  return createElement(PaletteItem, {
    className: isEditing ? 'is-selected' : undefined,
    as: "div",
    onClick: onStartEditing,
    ref: setPopoverAnchor,
    ...(isEditing ? {
      ...focusOutsideProps
    } : {
      style: {
        cursor: 'pointer'
      }
    })
  }, createElement(HStack, {
    justify: "flex-start"
  }, createElement(FlexItem, null, createElement(IndicatorStyled, {
    style: {
      background: value,
      color: 'transparent'
    }
  })), createElement(FlexItem, null, isEditing && !canOnlyChangeValues ? createElement(NameInput, {
    label: isGradient ? __('Gradient name') : __('Color name'),
    value: element.name,
    onChange: nextName => onChange({
      ...element,
      name: nextName,
      slug: slugPrefix + kebabCase(nextName !== null && nextName !== void 0 ? nextName : '')
    })
  }) : createElement(NameContainer, null, element.name)), isEditing && !canOnlyChangeValues && createElement(FlexItem, null, createElement(RemoveButton, {
    isSmall: true,
    icon: lineSolid,
    label: __('Remove color'),
    onClick: onRemove
  }))), isEditing && createElement(ColorPickerPopover, {
    isGradient: isGradient,
    onChange: onChange,
    element: element,
    popoverProps: popoverProps
  }));
}

/**
 * Checks if a color or gradient is a temporary element by testing against default values.
 */
export function isTemporaryElement(slugPrefix, {
  slug,
  color,
  gradient
}) {
  const regex = new RegExp(`^${slugPrefix}color-([\\d]+)$`);

  // If the slug matches the temporary name regex,
  // check if the color or gradient matches the default value.
  if (regex.test(slug)) {
    // The order is important as gradient elements
    // contain a color property.
    if (!!gradient) {
      return gradient === DEFAULT_GRADIENT;
    }
    if (!!color) {
      return color === DEFAULT_COLOR;
    }
  }
  return false;
}
function PaletteEditListView({
  elements,
  onChange,
  editingElement,
  setEditingElement,
  canOnlyChangeValues,
  slugPrefix,
  isGradient,
  popoverProps
}) {
  // When unmounting the component if there are empty elements (the user did not complete the insertion) clean them.
  const elementsReference = useRef();
  useEffect(() => {
    elementsReference.current = elements;
  }, [elements]);
  useEffect(() => {
    return () => {
      if (elementsReference.current?.some(element => isTemporaryElement(slugPrefix, element))) {
        const newElements = elementsReference.current.filter(element => !isTemporaryElement(slugPrefix, element));
        onChange(newElements.length ? newElements : undefined);
      }
    };
    // Disable reason: adding the missing dependency here would cause breaking changes that will require
    // a heavier refactor to avoid. See https://github.com/WordPress/gutenberg/pull/43911
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  const debounceOnChange = useDebounce(onChange, 100);
  return createElement(VStack, {
    spacing: 3
  }, createElement(ItemGroup, {
    isRounded: true
  }, elements.map((element, index) => createElement(Option, {
    isGradient: isGradient,
    canOnlyChangeValues: canOnlyChangeValues,
    key: index,
    element: element,
    onStartEditing: () => {
      if (editingElement !== index) {
        setEditingElement(index);
      }
    },
    onChange: newElement => {
      debounceOnChange(elements.map((currentElement, currentIndex) => {
        if (currentIndex === index) {
          return newElement;
        }
        return currentElement;
      }));
    },
    onRemove: () => {
      setEditingElement(null);
      const newElements = elements.filter((_currentElement, currentIndex) => {
        if (currentIndex === index) {
          return false;
        }
        return true;
      });
      onChange(newElements.length ? newElements : undefined);
    },
    isEditing: index === editingElement,
    onStopEditing: () => {
      if (index === editingElement) {
        setEditingElement(null);
      }
    },
    slugPrefix: slugPrefix,
    popoverProps: popoverProps
  }))));
}
const EMPTY_ARRAY = [];

/**
 * Allows editing a palette of colors or gradients.
 *
 * ```jsx
 * import { PaletteEdit } from '@wordpress/components';
 * const MyPaletteEdit = () => {
 *   const [ controlledColors, setControlledColors ] = useState( colors );
 *
 *   return (
 *     <PaletteEdit
 *       colors={ controlledColors }
 *       onChange={ ( newColors?: Color[] ) => {
 *         setControlledColors( newColors );
 *       } }
 *       paletteLabel="Here is a label"
 *     />
 *   );
 * };
 * ```
 */
export function PaletteEdit({
  gradients,
  colors = EMPTY_ARRAY,
  onChange,
  paletteLabel,
  paletteLabelHeadingLevel = 2,
  emptyMessage,
  canOnlyChangeValues,
  canReset,
  slugPrefix = '',
  popoverProps
}) {
  const isGradient = !!gradients;
  const elements = isGradient ? gradients : colors;
  const [isEditing, setIsEditing] = useState(false);
  const [editingElement, setEditingElement] = useState(null);
  const isAdding = isEditing && !!editingElement && elements[editingElement] && !elements[editingElement].slug;
  const elementsLength = elements.length;
  const hasElements = elementsLength > 0;
  const debounceOnChange = useDebounce(onChange, 100);
  const onSelectPaletteItem = useCallback((value, newEditingElementIndex) => {
    const selectedElement = newEditingElementIndex === undefined ? undefined : elements[newEditingElementIndex];
    const key = isGradient ? 'gradient' : 'color';
    // Ensures that the index returned matches a known element value.
    if (!!selectedElement && selectedElement[key] === value) {
      setEditingElement(newEditingElementIndex);
    } else {
      setIsEditing(true);
    }
  }, [isGradient, elements]);
  return createElement(PaletteEditStyles, null, createElement(PaletteHStackHeader, null, createElement(PaletteHeading, {
    level: paletteLabelHeadingLevel
  }, paletteLabel), createElement(PaletteActionsContainer, null, hasElements && isEditing && createElement(DoneButton, {
    isSmall: true,
    onClick: () => {
      setIsEditing(false);
      setEditingElement(null);
    }
  }, __('Done')), !canOnlyChangeValues && createElement(Button, {
    isSmall: true,
    isPressed: isAdding,
    icon: plus,
    label: isGradient ? __('Add gradient') : __('Add color'),
    onClick: () => {
      const tempOptionName = getNameForPosition(elements, slugPrefix);
      if (!!gradients) {
        onChange([...gradients, {
          gradient: DEFAULT_GRADIENT,
          name: tempOptionName,
          slug: slugPrefix + kebabCase(tempOptionName)
        }]);
      } else {
        onChange([...colors, {
          color: DEFAULT_COLOR,
          name: tempOptionName,
          slug: slugPrefix + kebabCase(tempOptionName)
        }]);
      }
      setIsEditing(true);
      setEditingElement(elements.length);
    }
  }), hasElements && (!isEditing || !canOnlyChangeValues || canReset) && createElement(DropdownMenu, {
    icon: moreVertical,
    label: isGradient ? __('Gradient options') : __('Color options'),
    toggleProps: {
      isSmall: true
    }
  }, ({
    onClose
  }) => createElement(Fragment, null, createElement(NavigableMenu, {
    role: "menu"
  }, !isEditing && createElement(Button, {
    variant: "tertiary",
    onClick: () => {
      setIsEditing(true);
      onClose();
    },
    className: "components-palette-edit__menu-button"
  }, __('Show details')), !canOnlyChangeValues && createElement(Button, {
    variant: "tertiary",
    onClick: () => {
      setEditingElement(null);
      setIsEditing(false);
      onChange();
      onClose();
    },
    className: "components-palette-edit__menu-button"
  }, isGradient ? __('Remove all gradients') : __('Remove all colors')), canReset && createElement(Button, {
    variant: "tertiary",
    onClick: () => {
      setEditingElement(null);
      onChange();
      onClose();
    }
  }, isGradient ? __('Reset gradient') : __('Reset colors'))))))), hasElements && createElement(Fragment, null, isEditing && createElement(PaletteEditListView, {
    canOnlyChangeValues: canOnlyChangeValues,
    elements: elements
    // @ts-expect-error TODO: Don't know how to resolve
    ,
    onChange: onChange,
    editingElement: editingElement,
    setEditingElement: setEditingElement,
    slugPrefix: slugPrefix,
    isGradient: isGradient,
    popoverProps: popoverProps
  }), !isEditing && editingElement !== null && createElement(ColorPickerPopover, {
    isGradient: isGradient,
    onClose: () => setEditingElement(null),
    onChange: newElement => {
      debounceOnChange(
      // @ts-expect-error TODO: Don't know how to resolve
      elements.map((currentElement, currentIndex) => {
        if (currentIndex === editingElement) {
          return newElement;
        }
        return currentElement;
      }));
    },
    element: elements[editingElement !== null && editingElement !== void 0 ? editingElement : -1],
    popoverProps: popoverProps
  }), !isEditing && (isGradient ? createElement(GradientPicker, {
    __nextHasNoMargin: true,
    gradients: gradients,
    onChange: onSelectPaletteItem,
    clearable: false,
    disableCustomGradients: true
  }) : createElement(ColorPalette, {
    colors: colors,
    onChange: onSelectPaletteItem,
    clearable: false,
    disableCustomColors: true
  }))), !hasElements && emptyMessage);
}
export default PaletteEdit;
//# sourceMappingURL=index.js.map