import { pipe } from "fp-ts-esm/function";
import * as S from "fp-ts-esm/string";
import lodash from "lodash";
import { LATEST_VERSION, Link_Target, Node_Type, TextStyle_TextAlignment } from "./../../ricos-schema/legacy/wix/rich_content/v1/index.js";
import { uuid } from "./../../wix-rich-content-common/index.js";
import { fromEntries } from "../utils.js";
import { generateId } from "./generate-random-id.js";
const createNode = (type, {
  nodes,
  data,
  style,
  id
}) => ({
  type,
  id: id ?? generateId(),
  nodes,
  ...dataByNodeType(type, data),
  style
});
const dataByNodeType = (type, data) => {
  const map = {
    [Node_Type.CODE_BLOCK]: { codeBlockData: data },
    [Node_Type.DIVIDER]: { dividerData: data },
    [Node_Type.HEADING]: { headingData: data },
    [Node_Type.FILE]: { fileData: data },
    [Node_Type.GALLERY]: { galleryData: data },
    [Node_Type.GIF]: { gifData: data },
    [Node_Type.HTML]: { htmlData: data },
    [Node_Type.IMAGE]: { imageData: data },
    [Node_Type.BUTTON]: { buttonData: data },
    [Node_Type.PARAGRAPH]: { paragraphData: data },
    [Node_Type.POLL]: { pollData: data },
    [Node_Type.TEXT]: { textData: data },
    [Node_Type.VIDEO]: { videoData: data },
    [Node_Type.AUDIO]: { audioData: data },
    [Node_Type.EMBED]: { embedData: data },
    [Node_Type.APP_EMBED]: { appEmbedData: data },
    [Node_Type.LINK_PREVIEW]: { linkPreviewData: data },
    [Node_Type.TABLE]: { tableData: data },
    [Node_Type.TABLE_CELL]: { tableCellData: data },
    [Node_Type.COLLAPSIBLE_LIST]: { collapsibleListData: data },
    [Node_Type.BULLETED_LIST]: {
      bulletedListData: data
    },
    [Node_Type.CHECKBOX_LIST]: {
      checkboxListData: data
    },
    [Node_Type.ORDERED_LIST]: {
      orderedListData: data
    }
  };
  return map[type];
};
const createParagraphNode = (nodes = [], data, style) => createNode(Node_Type.PARAGRAPH, {
  nodes,
  data: {
    textStyle: { textAlignment: TextStyle_TextAlignment.AUTO },
    ...data
  },
  style
});
const createTextNode = (text, decorations = []) => createNode(Node_Type.TEXT, { nodes: [], id: "", data: { text, decorations } });
const createHeadingNode = (nodes = [], data) => createNode(Node_Type.HEADING, {
  nodes,
  data: {
    textStyle: { textAlignment: TextStyle_TextAlignment.AUTO },
    ...data
  }
});
const createCaptionNode = (nodes) => createNode(Node_Type.CAPTION, {
  data: {},
  nodes
});
const createTableNode = (nodes, data = {}) => {
  return createNode(Node_Type.TABLE, { nodes, data });
};
const createTableRowNode = (nodes) => {
  return createNode(Node_Type.TABLE_ROW, { nodes, data: {} });
};
const createTableCellNode = (nodes) => {
  return createNode(Node_Type.TABLE_CELL, { nodes, data: {} });
};
const createDecoration = (type, data = {}) => ({ type, ...data });
const initializeMetadata = ({
  version,
  id = uuid(),
  createdTimestamp,
  updatedTimestamp
} = {}) => ({
  version: version || LATEST_VERSION,
  createdTimestamp: createdTimestamp || /* @__PURE__ */ new Date(),
  updatedTimestamp: updatedTimestamp || /* @__PURE__ */ new Date(),
  id
});
const reduceDecorations = (decorations) => {
  const reducedDecorationsMap = decorations.reduce(
    (decorationMap, { type, ...data }) => {
      const currentDecoration = decorationMap[type] || { type };
      const nextDecoration = { type, ...data };
      return {
        ...decorationMap,
        [type]: lodash.merge(currentDecoration, nextDecoration)
      };
    },
    {}
  );
  const reducedDecorations = Object.values(reducedDecorationsMap);
  return reducedDecorations;
};
const toLinkTarget = (target = "BLANK") => {
  switch (pipe(target, S.toUpperCase, S.replace("_", ""))) {
    case "SELF": {
      return Link_Target.SELF;
    }
    case "BLANK": {
      return Link_Target.BLANK;
    }
    case "PARENT": {
      return Link_Target.PARENT;
    }
    case "TOP": {
      return Link_Target.TOP;
    }
    default: {
      return Link_Target.BLANK;
    }
  }
};
const createLink = ({
  url,
  rel,
  target,
  anchor
}) => {
  const relValues = (typeof rel === "string" ? rel : "").split(" ").filter((key) => ["nofollow", "sponsored", "ugc", "noreferrer"].includes(key)).map((key) => [key, true]) || [];
  return {
    anchor,
    url,
    rel: relValues.length > 0 ? fromEntries(relValues) : void 0,
    target: toLinkTarget(typeof target === "string" ? target : void 0)
  };
};
const parseLink = (link) => ({
  anchor: link?.anchor,
  url: link?.url,
  rel: link?.rel && Object.entries(link.rel).flatMap(([key, value]) => value ? key : []).join(" "),
  target: link?.target && "_" + link.target.toLowerCase(),
  customData: link?.customData
});
const last = (arr) => arr.length > 0 ? arr[arr.length - 1] : null;
const partitionBy = (isSeparator, isPartition, Separator, Partition, addToPartition) => (nodes) => nodes.reduce((partitions, node) => {
  if (isSeparator(node)) {
    partitions.push(Separator(node));
  } else {
    let lastPartition = last(partitions);
    if (!lastPartition || !isPartition(lastPartition)) {
      const partition = Partition(node);
      partitions.push(partition);
      lastPartition = last(partitions);
    }
    addToPartition(lastPartition, node);
  }
  return partitions;
}, []);
const getRangeStyle = (range) => {
  try {
    const style = JSON.parse(range.style);
    return { styleKey: Object.keys(style)[0], styleValue: Object.values(style)[0] };
  } catch {
    return { styleKey: range.style, styleValue: range.style };
  }
};
const removeOverlappingRangesWithSameStyle = (ranges) => {
  const isOverlapping = (a, b) => a.styleKey === b.styleKey && a.styleValue === b.styleValue && a.offset <= b.offset + b.length && a.offset + a.length >= b.offset;
  return ranges.map(
    // @ts-ignore $TSFixMe
    (range) => ({
      ...range,
      ...getRangeStyle(range)
    })
  ).reduce((acc, range) => {
    const filteredRanges = acc.filter((r) => {
      return !(range.styleKey === r.styleKey && r.offset >= range.offset && r.offset + r.length <= range.offset + range.length);
    });
    return mergeRangeWithList(filteredRanges, range, isOverlapping);
  }, []).reduce((acc, range) => {
    return adjustAndAddRange(acc, range);
  }, []).map(({ styleKey, styleValue, ...range }) => range);
};
const adjustAndAddRange = (existingRanges, newRange) => {
  const isOverlapping = (a, b) => (a.styleKey === b.styleKey || `NOT_${a.styleKey}` === b.styleKey || a.styleKey === `NOT_${b.styleKey}`) && a.offset <= b.offset + b.length && a.offset + a.length >= b.offset;
  const adjust = (existing, newR) => {
    if (isOverlapping(existing, newR)) {
      if (existing.offset === newR.offset) {
        return {
          ...existing,
          offset: newR.offset + newR.length,
          length: existing.length - newR.length
        };
      } else if (existing.offset > newR.offset) {
        const overlapLength = newR.offset + newR.length - existing.offset;
        return {
          ...existing,
          offset: existing.offset + overlapLength,
          length: existing.length - overlapLength
        };
      } else {
        return { ...existing, length: newR.offset - existing.offset };
      }
    }
    return existing;
  };
  const adjustedRanges = existingRanges.map((range) => adjust(range, newRange)).filter((range) => range.length > 0);
  adjustedRanges.push(newRange);
  return adjustedRanges;
};
const mergeOverlappingEntityRanges = (entityRanges) => {
  const isOverlapping = (a, b) => a.key === b.key && a.offset <= b.offset + b.length && a.offset + a.length >= b.offset;
  return entityRanges.reduce(
    (acc, range) => mergeRangeWithList(acc, range, isOverlapping),
    []
  );
};
const mergeRangeWithList = (ranges, newRange, isOverlapping) => {
  const mergeRanges = (a, b) => ({
    ...a,
    offset: Math.min(a.offset, b.offset),
    length: Math.max(a.offset + a.length, b.offset + b.length) - Math.min(a.offset, b.offset)
  });
  const result = ranges.reduce(
    (acc, currentRange) => {
      if (isOverlapping(acc.currentMerging, currentRange)) {
        acc.currentMerging = mergeRanges(acc.currentMerging, currentRange);
        return acc;
      }
      acc.ranges.push(currentRange);
      return acc;
    },
    { ranges: [], currentMerging: newRange }
  );
  result.ranges.push(result.currentMerging);
  return result.ranges;
};
const generateRangeMap = (ranges) => {
  const rangeMap = {};
  ranges.forEach(({ offset, length, ...props }) => {
    rangeMap[offset] = [...rangeMap[offset] || [], { action: "start", ...props, length }];
    rangeMap[offset + length] = [
      ...rangeMap[offset + length] || [],
      { action: "end", ...props, length }
    ];
  });
  const normalizedRangeMap = normalizeRangeMap(rangeMap);
  if (!normalizedRangeMap[0]) {
    normalizedRangeMap[0] = [];
  }
  return normalizedRangeMap;
};
const getRangeEntityId = (entry) => {
  if ("key" in entry) {
    return entry.key;
  }
  return entry.style;
};
const isEntryOverlapping = (current, next) => {
  return getRangeEntityId(current) === getRangeEntityId(next) && current.action === "end" && next.action === "start" && // Ranges of length 1 are not overlapping
  current.length !== 1 && next.length !== 1;
};
const getStyleKeyVariation = (entry) => {
  if ("key" in entry) {
    return entry.key;
  }
  try {
    const style = JSON.parse(entry.style);
    return Object.keys(style)[0];
  } catch {
    return entry.style;
  }
};
const getStyleKeyValue = (entry) => {
  if ("key" in entry) {
    return entry.key;
  }
  return entry.style;
};
const mergeAdjacentRanges = (rangeMap) => {
  const newRangeMap = lodash.cloneDeep(rangeMap);
  Object.keys(newRangeMap).forEach((key) => {
    const intKey = parseInt(key);
    if (!newRangeMap[intKey + 1]) {
      return;
    }
    const hasMultipleSameStyleEntries = newRangeMap[intKey].some((current) => {
      return newRangeMap[intKey].some(
        (next) => current === next ? false : getStyleKeyVariation(current) === getStyleKeyVariation(next)
      );
    });
    if (hasMultipleSameStyleEntries) {
      return;
    }
    const currentRangeBeforeUpdate = newRangeMap[intKey];
    newRangeMap[intKey] = newRangeMap[intKey].filter((current) => {
      return newRangeMap[intKey + 1].every((next) => !isEntryOverlapping(current, next));
    });
    newRangeMap[intKey + 1] = newRangeMap[intKey + 1].filter((next) => {
      return currentRangeBeforeUpdate.every((current) => !isEntryOverlapping(current, next));
    });
  });
  return Object.entries(newRangeMap).reduce(
    (acc, [key, value]) => {
      return {
        ...acc,
        [key]: value.map((v) => ({ ...v, length: void 0 }))
      };
    },
    {}
  );
};
const moveEndOfRanges = (rangeMap) => {
  const newRangeMap = lodash.cloneDeep(rangeMap);
  Object.keys(newRangeMap).reverse().forEach((k, index, allKeys) => {
    const key = parseInt(k);
    for (const current of newRangeMap[key]) {
      if (current.action !== "end") {
        continue;
      }
      const currentStyleKey = getStyleKeyVariation(current);
      let isMovable = false;
      let hasValidStart = false;
      const sameStyleStartInSameKey = newRangeMap[key].find((entry) => {
        return getStyleKeyValue(entry) === getStyleKeyValue(current) && entry.action === "start";
      });
      if (sameStyleStartInSameKey) {
        newRangeMap[key] = newRangeMap[key].filter(
          (entry) => ![current, sameStyleStartInSameKey].includes(entry)
        );
        continue;
      }
      for (let i = index + 1; i < allKeys.length; i++) {
        const prevKey = Number(allKeys[i]);
        hasValidStart = newRangeMap[prevKey].some((prev) => {
          return prev.action === "start" && getRangeEntityId(current) === getRangeEntityId(prev);
        });
        if (hasValidStart) {
          break;
        }
        isMovable = newRangeMap[prevKey].some((prev) => {
          const prevStyleKey = getStyleKeyVariation(prev);
          return prev.action === "start" && currentStyleKey === prevStyleKey && getRangeEntityId(current) !== getRangeEntityId(prev);
        });
        if (isMovable) {
          newRangeMap[prevKey].push(current);
          break;
        }
      }
      if (hasValidStart) {
        continue;
      }
      if (isMovable) {
        newRangeMap[key] = newRangeMap[key].filter((entry) => entry !== current);
      }
    }
  });
  return newRangeMap;
};
const normalizeRangeMap = (rangeMap) => {
  const newRangeMap = moveEndOfRanges(mergeAdjacentRanges(rangeMap));
  Object.keys(newRangeMap).forEach((key) => {
    if (newRangeMap[Number(key)]?.length === 0) {
      delete newRangeMap[Number(key)];
    }
  });
  return newRangeMap;
};
export {
  createCaptionNode,
  createDecoration,
  createHeadingNode,
  createLink,
  createNode,
  createParagraphNode,
  createTableCellNode,
  createTableNode,
  createTableRowNode,
  createTextNode,
  dataByNodeType,
  generateRangeMap,
  initializeMetadata,
  mergeOverlappingEntityRanges,
  normalizeRangeMap,
  parseLink,
  partitionBy,
  reduceDecorations,
  removeOverlappingRangesWithSameStyle
};
//# sourceMappingURL=node-utils.js.map
