/**
 * @typedef SortAttrsParams
 * @property {ReadonlyArray<string>=} order
 * @property {'front' | 'alphabetical'=} xmlnsOrder
 */

export const name = 'sortAttrs';
export const description = 'Sort element attributes for better compression';

/**
 * Sort element attributes for better compression
 *
 * @author Nikolay Frantsev
 *
 * @type {import('../lib/types.js').Plugin<SortAttrsParams>}
 */
export const fn = (_root, params) => {
  const {
    order = [
      'id',
      'width',
      'height',
      'x',
      'x1',
      'x2',
      'y',
      'y1',
      'y2',
      'cx',
      'cy',
      'r',
      'fill',
      'stroke',
      'marker',
      'd',
      'points',
    ],
    xmlnsOrder = 'front',
  } = params;

  /**
   * @param {string} name
   * @returns {number}
   */
  const getNsPriority = (name) => {
    if (xmlnsOrder === 'front') {
      // put xmlns first
      if (name === 'xmlns') {
        return 3;
      }
      // xmlns:* attributes second
      if (name.startsWith('xmlns:')) {
        return 2;
      }
    }
    // other namespaces after and sort them alphabetically
    if (name.includes(':')) {
      return 1;
    }
    // other attributes
    return 0;
  };

  /**
   * @param {[string, string]} param0
   * @param {[string, string]} param1
   * @returns {number}
   */
  const compareAttrs = ([aName], [bName]) => {
    // sort namespaces
    const aPriority = getNsPriority(aName);
    const bPriority = getNsPriority(bName);
    const priorityNs = bPriority - aPriority;
    if (priorityNs !== 0) {
      return priorityNs;
    }
    // extract the first part from attributes
    // for example "fill" from "fill" and "fill-opacity"
    const [aPart] = aName.split('-');
    const [bPart] = bName.split('-');
    // rely on alphabetical sort when the first part is the same
    if (aPart !== bPart) {
      const aInOrderFlag = order.includes(aPart) ? 1 : 0;
      const bInOrderFlag = order.includes(bPart) ? 1 : 0;
      // sort by position in order param
      if (aInOrderFlag === 1 && bInOrderFlag === 1) {
        return order.indexOf(aPart) - order.indexOf(bPart);
      }
      // put attributes from order param before others
      const priorityOrder = bInOrderFlag - aInOrderFlag;
      if (priorityOrder !== 0) {
        return priorityOrder;
      }
    }
    // sort alphabetically
    return aName < bName ? -1 : 1;
  };

  return {
    element: {
      enter: (node) => {
        const attrs = Object.entries(node.attributes);
        attrs.sort(compareAttrs);
        /** @type {Record<string, string>} */
        const sortedAttributes = {};
        for (const [name, value] of attrs) {
          sortedAttributes[name] = value;
        }
        node.attributes = sortedAttributes;
      },
    },
  };
};
