"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.HierarchicalTermSelector = HierarchicalTermSelector;
exports.default = void 0;
exports.findTerm = findTerm;
exports.getFilterMatcher = getFilterMatcher;
exports.sortBySelected = sortBySelected;
var _i18n = require("@wordpress/i18n");
var _element = require("@wordpress/element");
var _notices = require("@wordpress/notices");
var _components = require("@wordpress/components");
var _data = require("@wordpress/data");
var _compose = require("@wordpress/compose");
var _coreData = require("@wordpress/core-data");
var _a11y = require("@wordpress/a11y");
var _htmlEntities = require("@wordpress/html-entities");
var _terms = require("../../utils/terms");
var _store = require("../../store");
var _jsxRuntime = require("react/jsx-runtime");
/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

/**
 * Module Constants
 */const DEFAULT_QUERY = {
  per_page: -1,
  orderby: 'name',
  order: 'asc',
  _fields: 'id,name,parent',
  context: 'view'
};
const MIN_TERMS_COUNT_FOR_FILTER = 8;
const EMPTY_ARRAY = [];

/**
 * Sort Terms by Selected.
 *
 * @param {Object[]} termsTree Array of terms in tree format.
 * @param {number[]} terms     Selected terms.
 *
 * @return {Object[]} Sorted array of terms.
 */
function sortBySelected(termsTree, terms) {
  const treeHasSelection = termTree => {
    if (terms.indexOf(termTree.id) !== -1) {
      return true;
    }
    if (undefined === termTree.children) {
      return false;
    }
    return termTree.children.map(treeHasSelection).filter(child => child).length > 0;
  };
  const termOrChildIsSelected = (termA, termB) => {
    const termASelected = treeHasSelection(termA);
    const termBSelected = treeHasSelection(termB);
    if (termASelected === termBSelected) {
      return 0;
    }
    if (termASelected && !termBSelected) {
      return -1;
    }
    if (!termASelected && termBSelected) {
      return 1;
    }
    return 0;
  };
  const newTermTree = [...termsTree];
  newTermTree.sort(termOrChildIsSelected);
  return newTermTree;
}

/**
 * Find term by parent id or name.
 *
 * @param {Object[]}      terms  Array of Terms.
 * @param {number|string} parent id.
 * @param {string}        name   Term name.
 * @return {Object} Term object.
 */
function findTerm(terms, parent, name) {
  return terms.find(term => {
    return (!term.parent && !parent || parseInt(term.parent) === parseInt(parent)) && term.name.toLowerCase() === name.toLowerCase();
  });
}

/**
 * Get filter matcher function.
 *
 * @param {string} filterValue Filter value.
 * @return {(function(Object): (Object|boolean))} Matcher function.
 */
function getFilterMatcher(filterValue) {
  const matchTermsForFilter = originalTerm => {
    if ('' === filterValue) {
      return originalTerm;
    }

    // Shallow clone, because we'll be filtering the term's children and
    // don't want to modify the original term.
    const term = {
      ...originalTerm
    };

    // Map and filter the children, recursive so we deal with grandchildren
    // and any deeper levels.
    if (term.children.length > 0) {
      term.children = term.children.map(matchTermsForFilter).filter(child => child);
    }

    // If the term's name contains the filterValue, or it has children
    // (i.e. some child matched at some point in the tree) then return it.
    if (-1 !== term.name.toLowerCase().indexOf(filterValue.toLowerCase()) || term.children.length > 0) {
      return term;
    }

    // Otherwise, return false. After mapping, the list of terms will need
    // to have false values filtered out.
    return false;
  };
  return matchTermsForFilter;
}

/**
 * Hierarchical term selector.
 *
 * @param {Object} props      Component props.
 * @param {string} props.slug Taxonomy slug.
 * @return {Element}        Hierarchical term selector component.
 */
function HierarchicalTermSelector({
  slug
}) {
  var _taxonomy$labels$sear, _taxonomy$name;
  const [adding, setAdding] = (0, _element.useState)(false);
  const [formName, setFormName] = (0, _element.useState)('');
  /**
   * @type {[number|'', Function]}
   */
  const [formParent, setFormParent] = (0, _element.useState)('');
  const [showForm, setShowForm] = (0, _element.useState)(false);
  const [filterValue, setFilterValue] = (0, _element.useState)('');
  const [filteredTermsTree, setFilteredTermsTree] = (0, _element.useState)([]);
  const debouncedSpeak = (0, _compose.useDebounce)(_a11y.speak, 500);
  const {
    hasCreateAction,
    hasAssignAction,
    terms,
    loading,
    availableTerms,
    taxonomy
  } = (0, _data.useSelect)(select => {
    var _post$_links, _post$_links2;
    const {
      getCurrentPost,
      getEditedPostAttribute
    } = select(_store.store);
    const {
      getTaxonomy,
      getEntityRecords,
      isResolving
    } = select(_coreData.store);
    const _taxonomy = getTaxonomy(slug);
    const post = getCurrentPost();
    return {
      hasCreateAction: _taxonomy ? (_post$_links = post._links?.['wp:action-create-' + _taxonomy.rest_base]) !== null && _post$_links !== void 0 ? _post$_links : false : false,
      hasAssignAction: _taxonomy ? (_post$_links2 = post._links?.['wp:action-assign-' + _taxonomy.rest_base]) !== null && _post$_links2 !== void 0 ? _post$_links2 : false : false,
      terms: _taxonomy ? getEditedPostAttribute(_taxonomy.rest_base) : EMPTY_ARRAY,
      loading: isResolving('getEntityRecords', ['taxonomy', slug, DEFAULT_QUERY]),
      availableTerms: getEntityRecords('taxonomy', slug, DEFAULT_QUERY) || EMPTY_ARRAY,
      taxonomy: _taxonomy
    };
  }, [slug]);
  const {
    editPost
  } = (0, _data.useDispatch)(_store.store);
  const {
    saveEntityRecord
  } = (0, _data.useDispatch)(_coreData.store);
  const availableTermsTree = (0, _element.useMemo)(() => sortBySelected((0, _terms.buildTermsTree)(availableTerms), terms),
  // Remove `terms` from the dependency list to avoid reordering every time
  // checking or unchecking a term.
  [availableTerms]);
  const {
    createErrorNotice
  } = (0, _data.useDispatch)(_notices.store);
  if (!hasAssignAction) {
    return null;
  }

  /**
   * Append new term.
   *
   * @param {Object} term Term object.
   * @return {Promise} A promise that resolves to save term object.
   */
  const addTerm = term => {
    return saveEntityRecord('taxonomy', slug, term, {
      throwOnError: true
    });
  };

  /**
   * Update terms for post.
   *
   * @param {number[]} termIds Term ids.
   */
  const onUpdateTerms = termIds => {
    editPost({
      [taxonomy.rest_base]: termIds
    });
  };

  /**
   * Handler for checking term.
   *
   * @param {number} termId
   */
  const onChange = termId => {
    const hasTerm = terms.includes(termId);
    const newTerms = hasTerm ? terms.filter(id => id !== termId) : [...terms, termId];
    onUpdateTerms(newTerms);
  };
  const onChangeFormName = value => {
    setFormName(value);
  };

  /**
   * Handler for changing form parent.
   *
   * @param {number|''} parentId Parent post id.
   */
  const onChangeFormParent = parentId => {
    setFormParent(parentId);
  };
  const onToggleForm = () => {
    setShowForm(!showForm);
  };
  const onAddTerm = async event => {
    var _taxonomy$labels$sing;
    event.preventDefault();
    if (formName === '' || adding) {
      return;
    }

    // Check if the term we are adding already exists.
    const existingTerm = findTerm(availableTerms, formParent, formName);
    if (existingTerm) {
      // If the term we are adding exists but is not selected select it.
      if (!terms.some(term => term === existingTerm.id)) {
        onUpdateTerms([...terms, existingTerm.id]);
      }
      setFormName('');
      setFormParent('');
      return;
    }
    setAdding(true);
    let newTerm;
    try {
      newTerm = await addTerm({
        name: formName,
        parent: formParent ? formParent : undefined
      });
    } catch (error) {
      createErrorNotice(error.message, {
        type: 'snackbar'
      });
      return;
    }
    const defaultName = slug === 'category' ? (0, _i18n.__)('Category') : (0, _i18n.__)('Term');
    const termAddedMessage = (0, _i18n.sprintf)( /* translators: %s: taxonomy name */
    (0, _i18n._x)('%s added', 'term'), (_taxonomy$labels$sing = taxonomy?.labels?.singular_name) !== null && _taxonomy$labels$sing !== void 0 ? _taxonomy$labels$sing : defaultName);
    (0, _a11y.speak)(termAddedMessage, 'assertive');
    setAdding(false);
    setFormName('');
    setFormParent('');
    onUpdateTerms([...terms, newTerm.id]);
  };
  const setFilter = value => {
    const newFilteredTermsTree = availableTermsTree.map(getFilterMatcher(value)).filter(term => term);
    const getResultCount = termsTree => {
      let count = 0;
      for (let i = 0; i < termsTree.length; i++) {
        count++;
        if (undefined !== termsTree[i].children) {
          count += getResultCount(termsTree[i].children);
        }
      }
      return count;
    };
    setFilterValue(value);
    setFilteredTermsTree(newFilteredTermsTree);
    const resultCount = getResultCount(newFilteredTermsTree);
    const resultsFoundMessage = (0, _i18n.sprintf)( /* translators: %d: number of results */
    (0, _i18n._n)('%d result found.', '%d results found.', resultCount), resultCount);
    debouncedSpeak(resultsFoundMessage, 'assertive');
  };
  const renderTerms = renderedTerms => {
    return renderedTerms.map(term => {
      return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
        className: "editor-post-taxonomies__hierarchical-terms-choice",
        children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.CheckboxControl, {
          __nextHasNoMarginBottom: true,
          checked: terms.indexOf(term.id) !== -1,
          onChange: () => {
            const termId = parseInt(term.id, 10);
            onChange(termId);
          },
          label: (0, _htmlEntities.decodeEntities)(term.name)
        }), !!term.children.length && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
          className: "editor-post-taxonomies__hierarchical-terms-subchoices",
          children: renderTerms(term.children)
        })]
      }, term.id);
    });
  };
  const labelWithFallback = (labelProperty, fallbackIsCategory, fallbackIsNotCategory) => {
    var _taxonomy$labels$labe;
    return (_taxonomy$labels$labe = taxonomy?.labels?.[labelProperty]) !== null && _taxonomy$labels$labe !== void 0 ? _taxonomy$labels$labe : slug === 'category' ? fallbackIsCategory : fallbackIsNotCategory;
  };
  const newTermButtonLabel = labelWithFallback('add_new_item', (0, _i18n.__)('Add new category'), (0, _i18n.__)('Add new term'));
  const newTermLabel = labelWithFallback('new_item_name', (0, _i18n.__)('Add new category'), (0, _i18n.__)('Add new term'));
  const parentSelectLabel = labelWithFallback('parent_item', (0, _i18n.__)('Parent Category'), (0, _i18n.__)('Parent Term'));
  const noParentOption = `— ${parentSelectLabel} —`;
  const newTermSubmitLabel = newTermButtonLabel;
  const filterLabel = (_taxonomy$labels$sear = taxonomy?.labels?.search_items) !== null && _taxonomy$labels$sear !== void 0 ? _taxonomy$labels$sear : (0, _i18n.__)('Search Terms');
  const groupLabel = (_taxonomy$name = taxonomy?.name) !== null && _taxonomy$name !== void 0 ? _taxonomy$name : (0, _i18n.__)('Terms');
  const showFilter = availableTerms.length >= MIN_TERMS_COUNT_FOR_FILTER;
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.Flex, {
    direction: "column",
    gap: "4",
    children: [showFilter && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.SearchControl, {
      __next40pxDefaultSize: true,
      __nextHasNoMarginBottom: true,
      label: filterLabel,
      value: filterValue,
      onChange: setFilter
    }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
      className: "editor-post-taxonomies__hierarchical-terms-list",
      tabIndex: "0",
      role: "group",
      "aria-label": groupLabel,
      children: renderTerms('' !== filterValue ? filteredTermsTree : availableTermsTree)
    }), !loading && hasCreateAction && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.FlexItem, {
      children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
        __next40pxDefaultSize: true,
        onClick: onToggleForm,
        className: "editor-post-taxonomies__hierarchical-terms-add",
        "aria-expanded": showForm,
        variant: "link",
        children: newTermButtonLabel
      })
    }), showForm && /*#__PURE__*/(0, _jsxRuntime.jsx)("form", {
      onSubmit: onAddTerm,
      children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.Flex, {
        direction: "column",
        gap: "4",
        children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.TextControl, {
          __next40pxDefaultSize: true,
          __nextHasNoMarginBottom: true,
          className: "editor-post-taxonomies__hierarchical-terms-input",
          label: newTermLabel,
          value: formName,
          onChange: onChangeFormName,
          required: true
        }), !!availableTerms.length && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.TreeSelect, {
          __next40pxDefaultSize: true,
          __nextHasNoMarginBottom: true,
          label: parentSelectLabel,
          noOptionLabel: noParentOption,
          onChange: onChangeFormParent,
          selectedId: formParent,
          tree: availableTermsTree
        }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.FlexItem, {
          children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Button, {
            __next40pxDefaultSize: true,
            variant: "secondary",
            type: "submit",
            className: "editor-post-taxonomies__hierarchical-terms-submit",
            children: newTermSubmitLabel
          })
        })]
      })
    })]
  });
}
var _default = exports.default = (0, _components.withFilters)('editor.PostTaxonomyType')(HierarchicalTermSelector);
//# sourceMappingURL=hierarchical-term-selector.js.map